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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +4 -0
- data/LICENSE +6 -0
- data/README.md +133 -439
- data/lib/analyzer.rb +48 -0
- data/lib/analyzers/morris.rb +268 -0
- data/lib/flex-cartesian/flex-cartesian-analyzer.rb +16 -0
- data/lib/flex-cartesian/flex-cartesian-core.rb +624 -0
- data/lib/flex-cartesian/flex-cartesian-deprecations.rb +13 -0
- data/lib/flex-cartesian/flex-cartesian-io.rb +192 -0
- data/lib/flex-cartesian/flex-cartesian-utilities.rb +126 -0
- data/lib/flex-cartesian.rb +13 -331
- data/lib/version.rb +3 -0
- data/lib/visualization/html.rb +217 -0
- metadata +48 -26
|
@@ -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:
|
|
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:
|
|
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:
|
|
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:
|
|
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.
|
|
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.
|
|
53
|
+
version: '1.0'
|
|
55
54
|
- !ruby/object:Gem::Dependency
|
|
56
|
-
name:
|
|
55
|
+
name: csv
|
|
57
56
|
requirement: !ruby/object:Gem::Requirement
|
|
58
57
|
requirements:
|
|
59
|
-
- - "
|
|
58
|
+
- - ">="
|
|
60
59
|
- !ruby/object:Gem::Version
|
|
61
|
-
version: '
|
|
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: '
|
|
67
|
+
version: '0'
|
|
69
68
|
- !ruby/object:Gem::Dependency
|
|
70
|
-
name:
|
|
69
|
+
name: roo
|
|
71
70
|
requirement: !ruby/object:Gem::Requirement
|
|
72
71
|
requirements:
|
|
73
|
-
- - "
|
|
72
|
+
- - ">="
|
|
74
73
|
- !ruby/object:Gem::Version
|
|
75
|
-
version: '
|
|
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: '
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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:
|
|
119
|
-
signing_key:
|
|
141
|
+
rubygems_version: 4.0.15
|
|
120
142
|
specification_version: 4
|
|
121
|
-
summary:
|
|
143
|
+
summary: Parametric system analysis as operations on Cartesian product for Ruby
|
|
122
144
|
test_files: []
|