rails_pulse 0.2.3 → 0.2.4

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 (37) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/app/controllers/concerns/chart_table_concern.rb +2 -3
  4. data/app/controllers/rails_pulse/application_controller.rb +10 -3
  5. data/app/controllers/rails_pulse/queries_controller.rb +1 -1
  6. data/app/controllers/rails_pulse/requests_controller.rb +2 -1
  7. data/app/controllers/rails_pulse/routes_controller.rb +1 -1
  8. data/app/helpers/rails_pulse/application_helper.rb +47 -2
  9. data/app/helpers/rails_pulse/chart_helper.rb +32 -2
  10. data/app/javascript/rails_pulse/application.js +3 -54
  11. data/app/javascript/rails_pulse/controllers/chart_controller.js +229 -0
  12. data/app/javascript/rails_pulse/controllers/index_controller.js +9 -14
  13. data/app/javascript/rails_pulse/controllers/pagination_controller.js +27 -33
  14. data/app/jobs/rails_pulse/backfill_summaries_job.rb +0 -2
  15. data/app/jobs/rails_pulse/cleanup_job.rb +0 -2
  16. data/app/jobs/rails_pulse/summary_job.rb +0 -2
  17. data/app/models/rails_pulse/queries/charts/average_query_times.rb +1 -1
  18. data/app/models/rails_pulse/requests/charts/average_response_times.rb +1 -1
  19. data/app/models/rails_pulse/routes/charts/average_response_times.rb +1 -1
  20. data/app/views/rails_pulse/components/_metric_card.html.erb +2 -2
  21. data/app/views/rails_pulse/components/_sparkline_stats.html.erb +1 -1
  22. data/app/views/rails_pulse/components/_table_pagination.html.erb +8 -6
  23. data/app/views/rails_pulse/csp_test/show.html.erb +1 -1
  24. data/app/views/rails_pulse/dashboard/charts/_bar_chart.html.erb +1 -1
  25. data/app/views/rails_pulse/dashboard/index.html.erb +4 -3
  26. data/app/views/rails_pulse/queries/index.html.erb +2 -1
  27. data/app/views/rails_pulse/queries/show.html.erb +2 -1
  28. data/app/views/rails_pulse/routes/index.html.erb +2 -1
  29. data/app/views/rails_pulse/routes/show.html.erb +2 -1
  30. data/config/importmap.rb +1 -1
  31. data/lib/rails_pulse/engine.rb +0 -30
  32. data/lib/rails_pulse/version.rb +1 -1
  33. data/public/rails-pulse-assets/csp-test.js +10 -10
  34. data/public/rails-pulse-assets/rails-pulse.js +48 -48
  35. data/public/rails-pulse-assets/rails-pulse.js.map +4 -4
  36. metadata +5 -25
  37. data/config/initializers/rails_charts_csp_patch.rb +0 -75
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d9f5f4407186848ab8ef3c75d8453dbe44d4f90fd641a50527b26cf9f4b10b82
4
- data.tar.gz: 283484459f73560755c16519e808d754bf23d82edb05ef7b98fcc396bdf3ead2
3
+ metadata.gz: 943ab1e08e81dbe5ede71b7d3c9ca526a65c9a0ce1767c7f9c37cee8883925cf
4
+ data.tar.gz: b3661c728215dc4759e6a3e04dd49dc113d13155972ed30f6ff35d0a2a07b10c
5
5
  SHA512:
6
- metadata.gz: b75fca5343d2080ecc547a5c8b68d2ec7ac431aea54505ad042721262c0f95c5955e229dcd338b011dfcdc208d7e6467b8b1c768efa83379efff5d7deaf13200
7
- data.tar.gz: a8e5d530527ed56d61029707825dd74885a742b132b41d5471c981f544c922e0431a6813d61e73db01dc85b17681aeb25c53347358cf93586b1b36af7874ab99
6
+ metadata.gz: db5d711b9d8931c6aa489b9069f05d16514a997b9798c743f2c2998611797dbc3dbdcfdcd12e69f65b2b3459292d1e7558075c02805aa0f49a398b85716736ea
7
+ data.tar.gz: 55ccd02b5a9bb13ba46e36e0051c2440d8026956743e02660968209c66e61fe25f4ed8c3dd04a77b08a6209df9e13c922f42e481ade7cf53447d89a93976e680
data/README.md CHANGED
@@ -601,7 +601,7 @@ Rails Pulse is built using modern, battle-tested technologies that ensure reliab
601
601
  - **[Turbo Frames](https://turbo.hotwired.dev/handbook/frames)** - Lazy loading and partial page updates for optimal performance
602
602
 
603
603
  ### **Data Visualization**
604
- - **[Rails Charts](https://github.com/railsjazz/rails_charts)** - Rails wrapper around Apache ECharts
604
+ - **[Apache ECharts](https://echarts.apache.org/)** - Powerful, interactive charting library
605
605
  - **[Lucide Icons](https://lucide.dev/)** - Beautiful, consistent iconography with pre-compiled SVG bundle
606
606
 
607
607
  ### **Asset Management**
@@ -2,7 +2,6 @@ module ChartTableConcern
2
2
  extend ActiveSupport::Concern
3
3
 
4
4
  included do
5
- include Pagy::Backend
6
5
  include TimeRangeConcern
7
6
  include ResponseRangeConcern
8
7
  include ZoomRangeConcern
@@ -41,7 +40,7 @@ module ChartTableConcern
41
40
  disabled_tags: session_disabled_tags,
42
41
  show_non_tagged: session[:show_non_tagged] != false,
43
42
  **chart_options
44
- ).to_rails_chart
43
+ ).to_chart_data
45
44
  end
46
45
 
47
46
  def setup_table_data(ransack_params)
@@ -52,7 +51,7 @@ module ChartTableConcern
52
51
  table_results = build_table_results
53
52
  handle_pagination
54
53
 
55
- @pagy, @table_data = pagy(table_results, limit: session_pagination_limit)
54
+ @pagy, @table_data = pagy(table_results, items: session_pagination_limit)
56
55
  end
57
56
 
58
57
  def setup_zoom_range_data
@@ -1,5 +1,12 @@
1
1
  module RailsPulse
2
2
  class ApplicationController < ActionController::Base
3
+ # Support both Pagy 8.x (Backend) and Pagy 9+ (Method)
4
+ if defined?(Pagy::Method)
5
+ include Pagy::Method
6
+ else
7
+ include Pagy::Backend
8
+ end
9
+
3
10
  before_action :authenticate_rails_pulse_user!
4
11
  before_action :set_show_non_tagged_default
5
12
  helper_method :session_global_filters, :session_disabled_tags
@@ -8,10 +15,10 @@ module RailsPulse
8
15
  limit = limit || params[:limit]
9
16
  session[:pagination_limit] = limit.to_i if limit.present?
10
17
 
11
- # Render JSON for direct API calls or AJAX requests (but not turbo frame requests)
12
- if (request.xhr? && !turbo_frame_request?) || (request.patch? && action_name == "set_pagination_limit")
18
+ # Render JSON for direct API calls or AJAX requests (but not turbo frame requests)
19
+ if (request.xhr? && !turbo_frame_request?) || (request.patch? && action_name == "set_pagination_limit")
13
20
  render json: { status: "ok" }
14
- end
21
+ end
15
22
  end
16
23
 
17
24
  def set_global_filters
@@ -163,7 +163,7 @@ module RailsPulse
163
163
  table_results = build_table_results
164
164
  handle_pagination
165
165
 
166
- @pagy, @table_data = pagy(table_results, limit: session_pagination_limit)
166
+ @pagy, @table_data = pagy(table_results, items: session_pagination_limit)
167
167
  end
168
168
 
169
169
  def handle_pagination
@@ -128,7 +128,8 @@ module RailsPulse
128
128
  table_results = build_table_results
129
129
  handle_pagination
130
130
 
131
- @pagy, @table_data = pagy(table_results, limit: session_pagination_limit)
131
+ # Use 'items:' for Pagy 8.x compatibility ('limit:' is for Pagy 43+)
132
+ @pagy, @table_data = pagy(table_results, items: session_pagination_limit)
132
133
  end
133
134
 
134
135
  def handle_pagination
@@ -142,7 +142,7 @@ module RailsPulse
142
142
  table_results = build_table_results
143
143
  handle_pagination
144
144
 
145
- @pagy, @table_data = pagy(table_results, limit: session_pagination_limit)
145
+ @pagy, @table_data = pagy(table_results, items: session_pagination_limit)
146
146
  end
147
147
 
148
148
  def handle_pagination
@@ -1,7 +1,5 @@
1
1
  module RailsPulse
2
2
  module ApplicationHelper
3
- include Pagy::Frontend
4
-
5
3
  include BreadcrumbsHelper
6
4
  include ChartHelper
7
5
  include FormattingHelper
@@ -10,6 +8,10 @@ module RailsPulse
10
8
  include FormHelper
11
9
  include TagsHelper
12
10
 
11
+ # Include Pagy frontend helpers for Pagy 8.x compatibility
12
+ # Pagy 43+ doesn't need this, but it doesn't hurt to include it
13
+ include Pagy::Frontend if defined?(Pagy::Frontend)
14
+
13
15
  # Replacement for lucide_icon helper that works with pre-compiled assets
14
16
  # Outputs a custom element that will be hydrated by Stimulus
15
17
  def rails_pulse_icon(name, options = {})
@@ -36,6 +38,49 @@ module RailsPulse
36
38
  # Backward compatibility alias - can be removed after migration
37
39
  alias_method :lucide_icon, :rails_pulse_icon
38
40
 
41
+ # Get items per page from Pagy instance (compatible with Pagy 8.x and 43+)
42
+ def pagy_items(pagy)
43
+ # Pagy 43+ uses options[:items] or has a limit method
44
+ if pagy.respond_to?(:options) && pagy.options.is_a?(Hash)
45
+ pagy.options[:items]
46
+ # Pagy 8.x uses vars[:items]
47
+ elsif pagy.respond_to?(:vars)
48
+ pagy.vars[:items]
49
+ # Fallback
50
+ else
51
+ pagy.limit || 10
52
+ end
53
+ end
54
+
55
+ # Get page URL from Pagy instance (compatible with Pagy 8.x and 43+)
56
+ def pagy_page_url(pagy, page_number)
57
+ # Pagy 43+ has page_url method
58
+ if pagy.respond_to?(:page_url)
59
+ pagy.page_url(page_number)
60
+ # Pagy 8.x requires using pagy_url_for helper
61
+ else
62
+ pagy_url_for(pagy, page_number)
63
+ end
64
+ end
65
+
66
+ # Get previous page number (compatible with Pagy 8.x and 43+)
67
+ def pagy_previous(pagy)
68
+ # Pagy 43+ uses 'previous'
69
+ if pagy.respond_to?(:previous)
70
+ pagy.previous
71
+ # Pagy 8.x uses 'prev'
72
+ elsif pagy.respond_to?(:prev)
73
+ pagy.prev
74
+ else
75
+ nil
76
+ end
77
+ end
78
+
79
+ # Get next page number (compatible with Pagy 8.x and 43+)
80
+ def pagy_next(pagy)
81
+ pagy.respond_to?(:next) ? pagy.next : nil
82
+ end
83
+
39
84
  # Make Rails Pulse routes available as rails_pulse in views
40
85
  def rails_pulse
41
86
  @rails_pulse_helper ||= RailsPulseHelper.new(self)
@@ -1,5 +1,30 @@
1
1
  module RailsPulse
2
2
  module ChartHelper
3
+ # Main chart rendering method - unified API for all chart types
4
+ # Uses Stimulus controller to handle chart initialization
5
+ def render_stimulus_chart(data, type:, **options)
6
+ chart_id = options[:id] || "rails-pulse-chart-#{SecureRandom.hex(8)}"
7
+ height = options[:height] || "400px"
8
+ width = options[:width] || "100%"
9
+ theme = options[:theme] || "railspulse"
10
+ chart_options = options[:options] || {}
11
+
12
+ # Build data attributes for Stimulus
13
+ stimulus_data = {
14
+ controller: "rails-pulse--chart",
15
+ rails_pulse__chart_type_value: type,
16
+ rails_pulse__chart_data_value: data.to_json,
17
+ rails_pulse__chart_options_value: chart_options.to_json,
18
+ rails_pulse__chart_theme_value: theme
19
+ }
20
+
21
+ content_tag(:div, "",
22
+ id: chart_id,
23
+ style: "height: #{height}; width: #{width};",
24
+ data: stimulus_data
25
+ )
26
+ end
27
+
3
28
  # Base chart options shared across all chart types
4
29
  def base_chart_options(units: nil, zoom: false)
5
30
  {
@@ -97,16 +122,21 @@ module RailsPulse
97
122
 
98
123
  private
99
124
 
125
+ # Wraps JavaScript function strings for later processing
126
+ def js_function(func_string)
127
+ "__FUNCTION_START__#{func_string}__FUNCTION_END__"
128
+ end
129
+
100
130
  def apply_tooltip_formatter(options, tooltip_formatter)
101
131
  return unless tooltip_formatter.present?
102
132
 
103
- options[:tooltip][:formatter] = RailsCharts.js(tooltip_formatter)
133
+ options[:tooltip][:formatter] = js_function(tooltip_formatter)
104
134
  end
105
135
 
106
136
  def apply_xaxis_formatter(options, xaxis_formatter)
107
137
  return unless xaxis_formatter.present?
108
138
 
109
- options[:xAxis][:axisLabel] ||= { formatter: RailsCharts.js(xaxis_formatter) }
139
+ options[:xAxis][:axisLabel] ||= { formatter: js_function(xaxis_formatter) }
110
140
  end
111
141
 
112
142
  def apply_zoom_configuration(options, zoom, zoom_start, zoom_end, chart_data)
@@ -12,6 +12,7 @@ import PopoverController from "./controllers/popover_controller";
12
12
  import FormController from "./controllers/form_controller";
13
13
 
14
14
  // Rails Pulse Controllers
15
+ import ChartController from "./controllers/chart_controller";
15
16
  import IndexController from "./controllers/index_controller";
16
17
  import ColorSchemeController from "./controllers/color_scheme_controller";
17
18
  import PaginationController from "./controllers/pagination_controller";
@@ -29,7 +30,7 @@ const application = Application.start();
29
30
  application.debug = false;
30
31
  window.Stimulus = application;
31
32
 
32
- // Make ECharts available globally for rails_charts gem
33
+ // Make ECharts available globally for chart rendering
33
34
  window.echarts = echarts;
34
35
 
35
36
  // Make Turbo available globally
@@ -42,6 +43,7 @@ application.register("rails-pulse--menu", MenuController);
42
43
  application.register("rails-pulse--popover", PopoverController);
43
44
  application.register("rails-pulse--form", FormController);
44
45
 
46
+ application.register("rails-pulse--chart", ChartController);
45
47
  application.register("rails-pulse--index", IndexController);
46
48
  application.register("rails-pulse--color-scheme", ColorSchemeController);
47
49
  application.register("rails-pulse--pagination", PaginationController);
@@ -96,59 +98,6 @@ echarts.registerTheme('railspulse', {
96
98
  "bar": { "itemStyle": { "barBorderWidth": 0 } }
97
99
  });
98
100
 
99
- // Chart resize functionality (moved from inline script for CSP compliance)
100
- window.addEventListener('resize', function() {
101
- if (window.RailsCharts && window.RailsCharts.charts) {
102
- Object.keys(window.RailsCharts.charts).forEach(function(chartID) {
103
- window.RailsCharts.charts[chartID].resize();
104
- });
105
- }
106
- });
107
-
108
- // Apply axis label colors based on current color scheme
109
- function applyChartAxisLabelColors() {
110
- if (!window.RailsCharts || !window.RailsCharts.charts) return;
111
- const scheme = document.documentElement.getAttribute('data-color-scheme');
112
- const isDark = scheme === 'dark';
113
- const axisColor = isDark ? '#ffffff' : '#999999';
114
- Object.keys(window.RailsCharts.charts).forEach(function(chartID) {
115
- const chart = window.RailsCharts.charts[chartID];
116
- try {
117
- chart.setOption({
118
- xAxis: { axisLabel: { color: axisColor } },
119
- yAxis: { axisLabel: { color: axisColor } }
120
- });
121
- } catch (e) {
122
- // noop
123
- }
124
- });
125
- }
126
-
127
- // Initial apply after charts initialize and on scheme changes
128
- document.addEventListener('DOMContentLoaded', () => {
129
- // run shortly after load to allow charts to initialize
130
- setTimeout(applyChartAxisLabelColors, 50);
131
- });
132
- document.addEventListener('rails-pulse:color-scheme-changed', applyChartAxisLabelColors);
133
-
134
- // Global function to initialize Rails Charts in any container.
135
- // This is needed as we render Rails Charts in Turbo Frames.
136
- window.initializeChartsInContainer = function(containerId) {
137
- requestAnimationFrame(() => {
138
- const container = containerId ? document.getElementById(containerId) : document;
139
- const scripts = container.querySelectorAll('script');
140
- scripts.forEach(script => {
141
- const content = script.textContent;
142
- const match = content.match(/function\s+(init_rails_charts_[a-f0-9]+)/);
143
- if (match && window[match[1]]) {
144
- window[match[1]]();
145
- }
146
- });
147
- // ensure colors are correct for any charts initialized in this container
148
- setTimeout(applyChartAxisLabelColors, 10);
149
- });
150
- };
151
-
152
101
  // Export for global access
153
102
  window.RailsPulse = {
154
103
  application,
@@ -0,0 +1,229 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ export default class extends Controller {
4
+ static values = {
5
+ type: String, // "bar", "line", "area", "sparkline"
6
+ data: Object, // Chart data
7
+ options: Object, // ECharts configuration
8
+ theme: String // ECharts theme
9
+ }
10
+
11
+ connect() {
12
+ this.initializeChart()
13
+ this.handleColorSchemeChange = this.onColorSchemeChange.bind(this)
14
+ document.addEventListener('rails-pulse:color-scheme-changed', this.handleColorSchemeChange)
15
+ }
16
+
17
+ disconnect() {
18
+ document.removeEventListener('rails-pulse:color-scheme-changed', this.handleColorSchemeChange)
19
+ this.disposeChart()
20
+ }
21
+
22
+ // Main initialization with retry logic
23
+ initializeChart() {
24
+ this.retryCount = 0
25
+ this.maxRetries = 100 // 5 seconds
26
+ this.attemptInit()
27
+ }
28
+
29
+ attemptInit() {
30
+ if (typeof echarts === 'undefined') {
31
+ this.retryCount++
32
+ if (this.retryCount >= this.maxRetries) {
33
+ console.error('[RailsPulse] echarts not loaded after 5 seconds for', this.element.id)
34
+ this.showError()
35
+ return
36
+ }
37
+ setTimeout(() => this.attemptInit(), 50)
38
+ return
39
+ }
40
+
41
+ this.renderChart()
42
+ }
43
+
44
+ renderChart() {
45
+ try {
46
+ // Initialize chart
47
+ this.chart = echarts.init(this.element, this.themeValue || 'railspulse')
48
+
49
+ // Build and set options
50
+ const config = this.buildChartConfig()
51
+ this.chart.setOption(config)
52
+
53
+ // Apply current color scheme
54
+ this.applyColorScheme()
55
+
56
+ // Dispatch event for other controllers (event-based communication)
57
+ document.dispatchEvent(new CustomEvent('stimulus:echarts:rendered', {
58
+ detail: {
59
+ containerId: this.element.id,
60
+ chart: this.chart,
61
+ controller: this
62
+ }
63
+ }))
64
+
65
+ // Responsive resize
66
+ this.resizeObserver = new ResizeObserver(() => {
67
+ if (this.chart) {
68
+ this.chart.resize()
69
+ }
70
+ })
71
+ this.resizeObserver.observe(this.element)
72
+
73
+ // Mark as rendered for tests
74
+ this.element.setAttribute('data-chart-rendered', 'true')
75
+
76
+ } catch (error) {
77
+ console.error('[RailsPulse] Error initializing chart:', error)
78
+ this.showError()
79
+ }
80
+ }
81
+
82
+ buildChartConfig() {
83
+ // Start with provided options
84
+ const config = { ...this.optionsValue }
85
+
86
+ // Process formatters (convert function strings to actual functions)
87
+ this.processFormatters(config)
88
+
89
+ // Set data (xAxis and series)
90
+ this.setChartData(config)
91
+
92
+ return config
93
+ }
94
+
95
+ setChartData(config) {
96
+ const data = this.dataValue
97
+
98
+ // Extract labels and values
99
+ const labels = Object.keys(data).map(k => {
100
+ const num = Number(k)
101
+ return isNaN(num) ? k : num
102
+ })
103
+
104
+ const values = Object.values(data).map(v => {
105
+ if (typeof v === 'object' && v !== null) {
106
+ return v.value !== undefined ? v.value : v
107
+ }
108
+ return v
109
+ })
110
+
111
+ // Set xAxis data
112
+ config.xAxis = config.xAxis || {}
113
+ config.xAxis.type = 'category'
114
+ config.xAxis.data = labels
115
+
116
+ // Set yAxis
117
+ config.yAxis = config.yAxis || {}
118
+ config.yAxis.type = 'value'
119
+
120
+ // Set series data
121
+ if (Array.isArray(config.series)) {
122
+ // If series is already an array, update first series
123
+ config.series[0] = config.series[0] || {}
124
+ config.series[0].type = this.typeValue
125
+ config.series[0].data = values
126
+ } else if (config.series && typeof config.series === 'object') {
127
+ // If series is a single object (from helper), convert to array
128
+ const seriesConfig = { ...config.series }
129
+ config.series = [{
130
+ type: this.typeValue,
131
+ data: values,
132
+ ...seriesConfig
133
+ }]
134
+ } else {
135
+ // No series provided, create default
136
+ config.series = [{
137
+ type: this.typeValue,
138
+ data: values
139
+ }]
140
+ }
141
+ }
142
+
143
+ processFormatters(config) {
144
+ // Process tooltip formatter
145
+ if (config.tooltip?.formatter && typeof config.tooltip.formatter === 'string') {
146
+ config.tooltip.formatter = this.parseFormatter(config.tooltip.formatter)
147
+ }
148
+
149
+ // Process xAxis formatter
150
+ if (config.xAxis?.axisLabel?.formatter && typeof config.xAxis.axisLabel.formatter === 'string') {
151
+ config.xAxis.axisLabel.formatter = this.parseFormatter(config.xAxis.axisLabel.formatter)
152
+ }
153
+
154
+ // Process yAxis formatter
155
+ if (config.yAxis?.axisLabel?.formatter && typeof config.yAxis.axisLabel.formatter === 'string') {
156
+ config.yAxis.axisLabel.formatter = this.parseFormatter(config.yAxis.axisLabel.formatter)
157
+ }
158
+ }
159
+
160
+ parseFormatter(formatterString) {
161
+ // Remove function markers if present
162
+ const cleanString = formatterString.replace(/__FUNCTION_START__|__FUNCTION_END__/g, '')
163
+
164
+ // If it's a function string, parse it
165
+ if (cleanString.trim().startsWith('function')) {
166
+ try {
167
+ // eslint-disable-next-line no-eval
168
+ return eval(`(${cleanString})`)
169
+ } catch (error) {
170
+ console.error('[RailsPulse] Error parsing formatter function:', error)
171
+ return cleanString
172
+ }
173
+ }
174
+ return cleanString
175
+ }
176
+
177
+ showError() {
178
+ this.element.classList.add('chart-error')
179
+ this.element.innerHTML = '<p class="text-subtle p-4">Chart failed to load</p>'
180
+ }
181
+
182
+ // Public accessor for chart instance
183
+ get chartInstance() {
184
+ return this.chart
185
+ }
186
+
187
+ disposeChart() {
188
+ if (this.resizeObserver) {
189
+ this.resizeObserver.disconnect()
190
+ }
191
+
192
+ if (this.chart) {
193
+ this.chart.dispose()
194
+ this.chart = null
195
+ }
196
+ }
197
+
198
+ // Action for dynamic updates
199
+ update(event) {
200
+ if (event.detail?.data) {
201
+ this.dataValue = event.detail.data
202
+ }
203
+ if (event.detail?.options) {
204
+ this.optionsValue = event.detail.options
205
+ }
206
+ if (this.chart) {
207
+ const config = this.buildChartConfig()
208
+ this.chart.setOption(config, true) // true = not merge
209
+ }
210
+ }
211
+
212
+ // Color scheme management
213
+ onColorSchemeChange() {
214
+ this.applyColorScheme()
215
+ }
216
+
217
+ applyColorScheme() {
218
+ if (!this.chart) return
219
+
220
+ const scheme = document.documentElement.getAttribute('data-color-scheme')
221
+ const isDark = scheme === 'dark'
222
+ const axisColor = isDark ? '#ffffff' : '#999999'
223
+
224
+ this.chart.setOption({
225
+ xAxis: { axisLabel: { color: axisColor } },
226
+ yAxis: { axisLabel: { color: axisColor } }
227
+ })
228
+ }
229
+ }
@@ -15,21 +15,16 @@ export default class extends Controller {
15
15
  originalSeriesOption = null;
16
16
 
17
17
  connect() {
18
- // Listen for the custom event 'chart:initialized' to set up the chart.
19
- // This event is sent from the RailsCharts library when the chart is ready.
18
+ // Listen for the custom event 'stimulus:echarts:rendered' to set up the chart.
19
+ // This event is dispatched by the chart controller when the chart is ready.
20
20
  this.handleChartInitialized = this.onChartInitialized.bind(this);
21
21
 
22
- document.addEventListener('chart:rendered', this.handleChartInitialized);
23
-
24
- // If the chart is already initialized (e.g., on back navigation), set up immediately
25
- if (window.RailsCharts?.charts?.[this.chartIdValue]) {
26
- this.setup();
27
- }
22
+ document.addEventListener('stimulus:echarts:rendered', this.handleChartInitialized);
28
23
  }
29
24
 
30
25
  disconnect() {
31
- // Remove the event listener from RailsCharts when the controller is disconnected
32
- document.removeEventListener('chart:rendered', this.handleChartInitialized);
26
+ // Remove the event listener when the controller is disconnected
27
+ document.removeEventListener('stimulus:echarts:rendered', this.handleChartInitialized);
33
28
 
34
29
  // Remove chart event listeners if they exist
35
30
  if (this.hasChartTarget && this.chartTarget) {
@@ -47,6 +42,8 @@ export default class extends Controller {
47
42
  // After the chart is initialized, set up the event listeners and data tracking
48
43
  onChartInitialized(event) {
49
44
  if (event.detail.containerId === this.chartIdValue) {
45
+ // Store the chart instance from the event
46
+ this.chart = event.detail.chart;
50
47
  this.setup();
51
48
  }
52
49
  }
@@ -56,7 +53,7 @@ export default class extends Controller {
56
53
  return; // Prevent multiple setups
57
54
  }
58
55
 
59
- // We need both the chart target in DOM and the chart object from RailsCharts
56
+ // We need both the chart target in DOM and the chart object from the event
60
57
  let hasTarget = false;
61
58
  try {
62
59
  hasTarget = !!this.chartTarget;
@@ -64,10 +61,8 @@ export default class extends Controller {
64
61
  hasTarget = false;
65
62
  }
66
63
 
67
- // Get the chart element which the RailsCharts library has created
68
- this.chart = window.RailsCharts.charts[this.chartIdValue];
69
-
70
64
  // Only proceed if we have BOTH the DOM target and the chart object
65
+ // (chart is set by onChartInitialized from the event)
71
66
  if (!hasTarget || !this.chart) {
72
67
  return;
73
68
  }