flex-cartesian 1.3.0 → 2.0.0.beta

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.
@@ -0,0 +1,217 @@
1
+ module FlexCartesianVisualization
2
+ require "csv"
3
+ require "json"
4
+ require "tempfile"
5
+
6
+ def visualize(format: :html, x:, y:, func:, output: nil, text: :dark, show_legend: false, show_z_title: true, show_grid: true, equal_axes: true, start_at_zero: true, show_plot_title: false, bg_color: 'transparent', font_color: nil, grid_color: nil, colorscale: 'Bluered')
7
+ raise "X-axis of visualization cannot be empty" unless x
8
+
9
+ funcs = Array(func).map(&:to_s)
10
+ raise "Functions of visualization cannot be empty" if funcs.empty?
11
+
12
+ # if colors aren't specified, we'll pick them based on theme
13
+ actual_font_color = font_color || (text == :dark ? '#333333' : '#edf5ff')
14
+ actual_grid_color = grid_color || (text == :dark ? 'rgba(0,0,0,0.15)' : 'rgba(255,255,255,0.15)')
15
+
16
+ case format
17
+ when :html
18
+ generate_html(
19
+ x: x.to_s,
20
+ y: y.to_s,
21
+ func: funcs,
22
+ output: output,
23
+ show_legend: show_legend,
24
+ show_z_title: show_z_title,
25
+ show_grid: show_grid,
26
+ equal_axes: equal_axes,
27
+ start_at_zero: start_at_zero,
28
+ show_plot_title: show_plot_title,
29
+ bg_color: bg_color,
30
+ font_color: actual_font_color,
31
+ grid_color: actual_grid_color,
32
+ colorscale: colorscale
33
+ )
34
+ else
35
+ raise "Incorrect visualize format #{format}"
36
+ end
37
+ end
38
+
39
+ def generate_html(x:, y: nil, func:, output:, show_legend:, show_z_title:, show_grid:, equal_axes:, start_at_zero:, show_plot_title:, bg_color:, font_color:, grid_color:, colorscale:)
40
+ temp_file = Tempfile.new
41
+ output(format: :csv, file: temp_file)
42
+ table = CSV.read(temp_file, headers: true, col_sep: ";")
43
+ temp_file.unlink
44
+
45
+ headers = table.headers.map { |h| h&.strip }
46
+
47
+ unless headers.include?(x)
48
+ raise ArgumentError, "Column '#{x}' not found in CSV. Available columns: #{headers.inspect}"
49
+ end
50
+
51
+ unless headers.include?(y)
52
+ raise ArgumentError, "Column '#{y}' not found in CSV. Available columns: #{headers.inspect}"
53
+ end
54
+
55
+ func.each do |f|
56
+ unless headers.include?(f)
57
+ raise ArgumentError, "Column '#{f}' not found in CSV. Available columns: #{headers.inspect}"
58
+ end
59
+ end
60
+
61
+ normalized_rows = table.map do |row|
62
+ h = {}
63
+ table.headers.each do |original_header|
64
+ normalized_header = original_header&.strip
65
+ h[normalized_header] = row[original_header]&.strip
66
+ end
67
+ h
68
+ end
69
+
70
+ x_values = normalized_rows.map { |r| numeric_or_string(r[x]) }.compact.uniq.sort_by { |v| sortable_key(v) }
71
+ y_values = normalized_rows.map { |r| numeric_or_string(r[y]) }.compact.uniq.sort_by { |v| sortable_key(v) }
72
+
73
+ x_index = x_values.each_with_index.to_h
74
+ y_index = y_values.each_with_index.to_h
75
+
76
+ z_matrices = {}
77
+ func.each do |f|
78
+ z_matrices[f] = Array.new(y_values.size) { Array.new(x_values.size, nil) }
79
+ end
80
+
81
+ normalized_rows.each do |row|
82
+ val_x = numeric_or_string(row[x])
83
+ val_y = numeric_or_string(row[y])
84
+
85
+ next if val_x.nil? || val_y.nil?
86
+
87
+ yi = y_index[val_y]
88
+ xi = x_index[val_x]
89
+
90
+ func.each do |f|
91
+ val_z = numeric_or_string(row[f])
92
+ z_matrices[f][yi][xi] = val_z unless val_z.nil?
93
+ end
94
+ end
95
+
96
+ plotly_data = func.map.with_index do |f, index|
97
+ {
98
+ type: "surface",
99
+ name: f,
100
+ x: x_values,
101
+ y: y_values,
102
+ z: z_matrices[f],
103
+ opacity: 0.85,
104
+ hovertemplate: "#{x}: %{x}<br>#{y}: %{y}<br>#{f}: %{z}<extra></extra>",
105
+ connectgaps: false,
106
+ showscale: index == 0 ? show_legend : false,
107
+ colorscale: colorscale,
108
+ contours: {
109
+ x: { show: show_grid, color: '#ffffff', width: 1 }, # 'color' is hardcoded to white - TODO: make it tunable
110
+ y: { show: show_grid, color: '#ffffff', width: 1 }
111
+ }
112
+ }
113
+ end
114
+
115
+ combined_func_names = func.join(", ")
116
+ zaxis_title_js = show_z_title ? "title: #{JSON.generate(combined_func_names)}," : "title: '',"
117
+ grid_flag = show_grid ? 'true' : 'false'
118
+ aspect_mode_js = equal_axes ? "aspectmode: 'cube'," : "aspectmode: 'auto',"
119
+ range_mode_js = start_at_zero ? "rangemode: 'tozero'," : ""
120
+ plot_title = show_plot_title ? "title: #{JSON.generate("#{combined_func_names} (#{x}, #{y})")}," : "title: '',"
121
+
122
+ plotly_bg = bg_color == 'transparent' ? 'rgba(0,0,0,0)' : bg_color
123
+
124
+ html = <<~HTML
125
+ <!DOCTYPE html>
126
+ <html>
127
+ <head>
128
+ <meta charset="UTF-8">
129
+ <title>Surface Plot</title>
130
+ <script src="https://cdn.plot.ly/plotly-2.35.2.min.js"></script>
131
+ <style>
132
+ html, body {
133
+ background-color: #{bg_color} !important;
134
+ margin: 0;
135
+ padding: 0;
136
+ width: 100%;
137
+ height: 100%;
138
+ font-family: Arial, sans-serif;
139
+ }
140
+ #chart {
141
+ width: 100vw;
142
+ height: 100vh;
143
+ }
144
+ </style>
145
+ </head>
146
+ <body>
147
+ <div id="chart"></div>
148
+ <script>
149
+ const data = #{JSON.generate(plotly_data)};
150
+
151
+ const layout = {
152
+ #{plot_title}
153
+ paper_bgcolor: '#{plotly_bg}',
154
+ plot_bgcolor: '#{plotly_bg}',
155
+ font: { color: '#{font_color}' }, // Применяется ко всему тексту (заголовок, названия осей, значения)
156
+ scene: {
157
+ #{aspect_mode_js}
158
+ xaxis: {
159
+ title: #{JSON.generate(x)},
160
+ showgrid: #{grid_flag},
161
+ zeroline: #{grid_flag},
162
+ gridcolor: '#{grid_color}', // Цвет линий сетки
163
+ zerolinecolor: '#{grid_color}', // Цвет нулевой линии
164
+ linecolor: '#{grid_color}', // Цвет линии самой оси
165
+ #{range_mode_js}
166
+ },
167
+ yaxis: {
168
+ title: #{JSON.generate(y)},
169
+ showgrid: #{grid_flag},
170
+ zeroline: #{grid_flag},
171
+ gridcolor: '#{grid_color}',
172
+ zerolinecolor: '#{grid_color}',
173
+ linecolor: '#{grid_color}',
174
+ #{range_mode_js}
175
+ },
176
+ zaxis: {
177
+ #{zaxis_title_js}
178
+ showgrid: #{grid_flag},
179
+ zeroline: #{grid_flag},
180
+ gridcolor: '#{grid_color}',
181
+ zerolinecolor: '#{grid_color}',
182
+ linecolor: '#{grid_color}',
183
+ #{range_mode_js}
184
+ }
185
+ },
186
+ margin: { l: 0, r: 0, b: 0, t: 50 }
187
+ };
188
+
189
+ Plotly.newPlot("chart", data, layout, { responsive: true });
190
+ </script>
191
+ </body>
192
+ </html>
193
+ HTML
194
+
195
+ output ? File.write(output, html) : STDOUT.write(html)
196
+ end
197
+
198
+ def numeric_or_string(value)
199
+ return nil if value.nil?
200
+
201
+ s = value.to_s.strip
202
+ return nil if s.empty?
203
+
204
+ Integer(s)
205
+ rescue ArgumentError
206
+ begin
207
+ Float(s)
208
+ rescue ArgumentError
209
+ s
210
+ end
211
+ end
212
+
213
+ def sortable_key(value)
214
+ value.is_a?(Numeric) ? [0, value] : [1, value.to_s]
215
+ end
216
+
217
+ end
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: flex-cartesian
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.0
4
+ version: 2.0.0.beta
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yury Rassokhin
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2025-08-19 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: colorize
@@ -25,7 +24,7 @@ dependencies:
25
24
  - !ruby/object:Gem::Version
26
25
  version: '0.8'
27
26
  - !ruby/object:Gem::Dependency
28
- name: ruby-progressbar
27
+ name: progressbar
29
28
  requirement: !ruby/object:Gem::Requirement
30
29
  requirements:
31
30
  - - "~>"
@@ -39,51 +38,67 @@ dependencies:
39
38
  - !ruby/object:Gem::Version
40
39
  version: '1.13'
41
40
  - !ruby/object:Gem::Dependency
42
- name: progressbar
41
+ name: method_source
43
42
  requirement: !ruby/object:Gem::Requirement
44
43
  requirements:
45
44
  - - "~>"
46
45
  - !ruby/object:Gem::Version
47
- version: '1.13'
46
+ version: '1.0'
48
47
  type: :runtime
49
48
  prerelease: false
50
49
  version_requirements: !ruby/object:Gem::Requirement
51
50
  requirements:
52
51
  - - "~>"
53
52
  - !ruby/object:Gem::Version
54
- version: '1.13'
53
+ version: '1.0'
55
54
  - !ruby/object:Gem::Dependency
56
- name: json
55
+ name: csv
57
56
  requirement: !ruby/object:Gem::Requirement
58
57
  requirements:
59
- - - "~>"
58
+ - - ">="
60
59
  - !ruby/object:Gem::Version
61
- version: '2.0'
60
+ version: '0'
62
61
  type: :runtime
63
62
  prerelease: false
64
63
  version_requirements: !ruby/object:Gem::Requirement
65
64
  requirements:
66
- - - "~>"
65
+ - - ">="
67
66
  - !ruby/object:Gem::Version
68
- version: '2.0'
67
+ version: '0'
69
68
  - !ruby/object:Gem::Dependency
70
- name: method_source
69
+ name: roo
71
70
  requirement: !ruby/object:Gem::Requirement
72
71
  requirements:
73
- - - "~>"
72
+ - - ">="
74
73
  - !ruby/object:Gem::Version
75
- version: '1.0'
74
+ version: '0'
76
75
  type: :runtime
77
76
  prerelease: false
78
77
  version_requirements: !ruby/object:Gem::Requirement
79
78
  requirements:
80
- - - "~>"
79
+ - - ">="
81
80
  - !ruby/object:Gem::Version
82
- version: '1.0'
83
- description: 'Flexible and human-friendly Cartesian product enumerator for Ruby. Supports
84
- functions and conditions on cartesian, dimensionality-agnostic/dimensionality-aware
85
- iterators, named dimensions, tabular output, lazy/eager evaluation, progress bar,
86
- import from JSON/YAML, and export to Markdown/CSV. Code example: https://github.com/Yuri-Rassokhin/flex-cartesian/blob/main/README.md#example'
81
+ version: '0'
82
+ - !ruby/object:Gem::Dependency
83
+ name: ast
84
+ requirement: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: '0'
89
+ type: :runtime
90
+ prerelease: false
91
+ version_requirements: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ version: '0'
96
+ description: 'A Ruby DSL for operations on Cartesian multidimensional spaces. Features
97
+ user-defined functions, space conditions, dimensionality-agnostic and dimensionality-aware
98
+ iterators; named dimensions; tabular output; lazy/eager evaluation; progress bar;
99
+ import from JSON/YAML/CSV/XLSX; export to Markdown/CSV; DoE with Parametric Behaviour
100
+ Blueprinting (PBB) to create blueprints of real systems; and visual heatmaps to
101
+ model and optimize real systems effortlessly. Code examples: https://github.com/Yuri-Rassokhin/flex-cartesian/tree/main/examples/13_chatgpt_semantic_shift/example.rb'
87
102
  email:
88
103
  - yuri.rassokhin@gmail.com
89
104
  executables: []
@@ -94,13 +109,21 @@ files:
94
109
  - Gemfile
95
110
  - LICENSE
96
111
  - README.md
112
+ - lib/analyzer.rb
113
+ - lib/analyzers/morris.rb
97
114
  - lib/flex-cartesian.rb
115
+ - lib/flex-cartesian/flex-cartesian-analyzer.rb
116
+ - lib/flex-cartesian/flex-cartesian-core.rb
117
+ - lib/flex-cartesian/flex-cartesian-deprecations.rb
118
+ - lib/flex-cartesian/flex-cartesian-io.rb
119
+ - lib/flex-cartesian/flex-cartesian-utilities.rb
120
+ - lib/version.rb
121
+ - lib/visualization/html.rb
98
122
  homepage: https://github.com/Yuri-Rassokhin/flex-cartesian
99
123
  licenses:
100
- - GPL-3.0
124
+ - GPL-3.0-only
101
125
  metadata:
102
126
  source_code_uri: https://github.com/Yuri-Rassokhin/flex-cartesian
103
- post_install_message:
104
127
  rdoc_options: []
105
128
  require_paths:
106
129
  - lib
@@ -115,8 +138,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
115
138
  - !ruby/object:Gem::Version
116
139
  version: '0'
117
140
  requirements: []
118
- rubygems_version: 3.2.33
119
- signing_key:
141
+ rubygems_version: 4.0.15
120
142
  specification_version: 4
121
- summary: Flexible and human-friendly Cartesian product enumerator for Ruby
143
+ summary: Parametric system analysis as operations on Cartesian product for Ruby
122
144
  test_files: []