ctioga 1.11.1
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.
- data/COPYING +340 -0
- data/ctioga/bin/ctable +28 -0
- data/ctioga/bin/ctioga +37 -0
- data/ctioga/doc/ctable.1 +156 -0
- data/ctioga/doc/ctioga.1 +2363 -0
- data/ctioga/examples/README +46 -0
- data/ctioga/examples/ctioga.gnuplot +4 -0
- data/ctioga/examples/ctioga_within_tioga.rb +53 -0
- data/ctioga/examples/ctiogarc.rb +24 -0
- data/ctioga/examples/include_1.rb +15 -0
- data/ctioga/examples/noise.dat +100 -0
- data/ctioga/examples/noise.rb +13 -0
- data/ctioga/examples/trig.csv +100 -0
- data/ctioga/examples/trig.dat +100 -0
- data/ctioga/examples/trig.rb +14 -0
- data/ctioga/examples/trigh.dat +100 -0
- data/ctioga/examples/trigh.rb +10 -0
- data/ctioga/examples/tutorial +763 -0
- data/ctioga/examples/tutorial.sh +269 -0
- data/ctioga/tests/README +14 -0
- data/ctioga/tests/axes.sh +40 -0
- data/ctioga/tests/basic.sh +11 -0
- data/ctioga/tests/draw.sh +24 -0
- data/ctioga/tests/histograms.sh +14 -0
- data/ctioga/tests/insets.sh +41 -0
- data/ctioga/tests/layouts.sh +29 -0
- data/ctioga/tests/legends.sh +113 -0
- data/ctioga/tests/styles.sh +43 -0
- data/ctioga/tests/test_style.sh +8 -0
- data/ctioga/tests/tests.sh +24 -0
- data/ctioga/tests/text_backend.sh +83 -0
- data/ctioga/tests/tioga_defaults.rb +18 -0
- data/lib/CTioga/axes.rb +904 -0
- data/lib/CTioga/backends.rb +88 -0
- data/lib/CTioga/boundaries.rb +224 -0
- data/lib/CTioga/ctable.rb +134 -0
- data/lib/CTioga/curve_style.rb +246 -0
- data/lib/CTioga/debug.rb +199 -0
- data/lib/CTioga/dimension.rb +133 -0
- data/lib/CTioga/elements.rb +17 -0
- data/lib/CTioga/elements/base.rb +84 -0
- data/lib/CTioga/elements/containers.rb +578 -0
- data/lib/CTioga/elements/curves.rb +368 -0
- data/lib/CTioga/elements/tioga_primitives.rb +440 -0
- data/lib/CTioga/layout.rb +595 -0
- data/lib/CTioga/legends.rb +29 -0
- data/lib/CTioga/legends/cmdline.rb +187 -0
- data/lib/CTioga/legends/item.rb +164 -0
- data/lib/CTioga/legends/style.rb +257 -0
- data/lib/CTioga/log.rb +73 -0
- data/lib/CTioga/movingarrays.rb +131 -0
- data/lib/CTioga/partition.rb +271 -0
- data/lib/CTioga/plot_style.rb +230 -0
- data/lib/CTioga/plotmaker.rb +1677 -0
- data/lib/CTioga/shortcuts.rb +69 -0
- data/lib/CTioga/structures.rb +82 -0
- data/lib/CTioga/styles.rb +140 -0
- data/lib/CTioga/themes.rb +581 -0
- data/lib/CTioga/themes/classical.rb +82 -0
- data/lib/CTioga/themes/demo.rb +63 -0
- data/lib/CTioga/themes/fits.rb +91 -0
- data/lib/CTioga/themes/mono.rb +33 -0
- data/lib/CTioga/tioga.rb +32 -0
- data/lib/CTioga/utils.rb +173 -0
- data/lib/MetaBuilder/Parameters/dates.rb +38 -0
- data/lib/MetaBuilder/Parameters/lists.rb +132 -0
- data/lib/MetaBuilder/Parameters/numbers.rb +69 -0
- data/lib/MetaBuilder/Parameters/strings.rb +86 -0
- data/lib/MetaBuilder/Parameters/styles.rb +75 -0
- data/lib/MetaBuilder/Qt4/Parameters/dates.rb +51 -0
- data/lib/MetaBuilder/Qt4/Parameters/numbers.rb +65 -0
- data/lib/MetaBuilder/Qt4/Parameters/strings.rb +106 -0
- data/lib/MetaBuilder/Qt4/parameter.rb +172 -0
- data/lib/MetaBuilder/Qt4/parameters.rb +9 -0
- data/lib/MetaBuilder/descriptions.rb +603 -0
- data/lib/MetaBuilder/factory.rb +101 -0
- data/lib/MetaBuilder/group.rb +57 -0
- data/lib/MetaBuilder/metabuilder.rb +10 -0
- data/lib/MetaBuilder/parameter.rb +374 -0
- data/lib/MetaBuilder/parameters.rb +11 -0
- data/lib/MetaBuilder/qt4.rb +8 -0
- data/lib/SciYAG/Backends/backend.rb +379 -0
- data/lib/SciYAG/Backends/binner.rb +168 -0
- data/lib/SciYAG/Backends/cache.rb +102 -0
- data/lib/SciYAG/Backends/dataset.rb +158 -0
- data/lib/SciYAG/Backends/descriptions.rb +469 -0
- data/lib/SciYAG/Backends/filters.rb +25 -0
- data/lib/SciYAG/Backends/filters/average.rb +134 -0
- data/lib/SciYAG/Backends/filters/cumulate.rb +37 -0
- data/lib/SciYAG/Backends/filters/filter.rb +70 -0
- data/lib/SciYAG/Backends/filters/norm.rb +39 -0
- data/lib/SciYAG/Backends/filters/smooth.rb +63 -0
- data/lib/SciYAG/Backends/filters/sort.rb +43 -0
- data/lib/SciYAG/Backends/filters/strip.rb +34 -0
- data/lib/SciYAG/Backends/filters/trim.rb +64 -0
- data/lib/SciYAG/Backends/gnuplot.rb +131 -0
- data/lib/SciYAG/Backends/math.rb +108 -0
- data/lib/SciYAG/Backends/mdb.rb +462 -0
- data/lib/SciYAG/Backends/multitext.rb +96 -0
- data/lib/SciYAG/Backends/source.rb +64 -0
- data/lib/SciYAG/Backends/text.rb +339 -0
- data/lib/SciYAG/backends.rb +16 -0
- metadata +191 -0
data/lib/CTioga/log.rb
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# log.rb, copyright (c) 2006 by Vincent Fourmond:
|
|
2
|
+
# The general logging functions for Ctioga
|
|
3
|
+
|
|
4
|
+
# This program is free software; you can redistribute it and/or modify
|
|
5
|
+
# it under the terms of the GNU General Public License as published by
|
|
6
|
+
# the Free Software Foundation; either version 2 of the License, or
|
|
7
|
+
# (at your option) any later version.
|
|
8
|
+
|
|
9
|
+
# This program is distributed in the hope that it will be useful,
|
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
# GNU General Public License for more details (in the COPYING file).
|
|
13
|
+
|
|
14
|
+
require 'logger'
|
|
15
|
+
require 'forwardable'
|
|
16
|
+
|
|
17
|
+
module CTioga
|
|
18
|
+
|
|
19
|
+
Version::register_svn_info('$Revision: 653 $', '$Date: 2007-11-15 21:58:42 +0100 (Thu, 15 Nov 2007) $')
|
|
20
|
+
|
|
21
|
+
# This module contains code to be included by PlotMaker for debugging.
|
|
22
|
+
# Even if it doesn't deserve a real module of it's own, it makes sense
|
|
23
|
+
# to have it separated anyway.
|
|
24
|
+
module Log
|
|
25
|
+
|
|
26
|
+
extend Forwardable
|
|
27
|
+
def_delegators :@@log, :debug, :warn, :info, :error, :fatal
|
|
28
|
+
|
|
29
|
+
def init_logger(stream = STDERR)
|
|
30
|
+
Logger::Formatter::Format.replace("[%4$s] %6$s\n")
|
|
31
|
+
@@log = Logger.new(stream)
|
|
32
|
+
@@log.level = Logger::WARN # Warnings and more only by default
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Simple accessor for the @@log class variable.
|
|
36
|
+
def logger
|
|
37
|
+
return @@log
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def logger_options(opt)
|
|
41
|
+
opt.on("-v", "--verbose", "Display useful information") do
|
|
42
|
+
@@log.level = Logger::INFO
|
|
43
|
+
end
|
|
44
|
+
opt.on("--debug", "Turn on debugging info") do
|
|
45
|
+
@@log.level = Logger::DEBUG
|
|
46
|
+
end
|
|
47
|
+
opt.on("--quiet", "Display only errors") do
|
|
48
|
+
@@log.level = Logger::ERROR
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# A logging replacement for system
|
|
53
|
+
def spawn(cmd, priority = :info)
|
|
54
|
+
retval = system(cmd)
|
|
55
|
+
self.send(priority, "Running #{cmd} -> " +
|
|
56
|
+
if retval
|
|
57
|
+
"success"
|
|
58
|
+
else
|
|
59
|
+
"failure"
|
|
60
|
+
end
|
|
61
|
+
)
|
|
62
|
+
return retval
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Returns a string suitable for identification of an object, but
|
|
66
|
+
# without instance variables.
|
|
67
|
+
def identify(obj)
|
|
68
|
+
return "#<#{obj.class} 0x%x>" % obj.object_id
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
end
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# movingarray.rb, copyright (c) 2007 by Vincent Fourmond:
|
|
2
|
+
# Moving arrays
|
|
3
|
+
|
|
4
|
+
# This program is free software; you can redistribute it and/or modify
|
|
5
|
+
# it under the terms of the GNU General Public License as published by
|
|
6
|
+
# the Free Software Foundation; either version 2 of the License, or
|
|
7
|
+
# (at your option) any later version.
|
|
8
|
+
|
|
9
|
+
# This program is distributed in the hope that it will be useful,
|
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
# GNU General Public License for more details (in the COPYING file).
|
|
13
|
+
|
|
14
|
+
require 'Dobjects/Dvector'
|
|
15
|
+
require 'Tioga/FigureConstants'
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
module CTioga
|
|
19
|
+
|
|
20
|
+
Version::register_svn_info('$Revision: 854 $', '$Date: 2008-11-27 23:21:51 +0100 (Thu, 27 Nov 2008) $')
|
|
21
|
+
|
|
22
|
+
# A helper function that converts user input into a Tioga
|
|
23
|
+
# color.
|
|
24
|
+
#
|
|
25
|
+
# TODO: allow designs such as in LaTeX xcolor: Red!10!Blue !
|
|
26
|
+
def self.get_tioga_color(str)
|
|
27
|
+
if str =~ /([\d.]+),([\d.]+),([\d.]+)/ # Array notation
|
|
28
|
+
color = Dobjects::Dvector.new([$1.to_f,$2.to_f,$3.to_f])
|
|
29
|
+
if color.max > 1
|
|
30
|
+
color *= 1.0/255.0
|
|
31
|
+
end
|
|
32
|
+
return color.to_a
|
|
33
|
+
elsif str =~ /#([0-9a-fA-F]{6})/ # HTML notation
|
|
34
|
+
return $1.scan(/../).map { |i| i.to_i(16)/255.0 }
|
|
35
|
+
else
|
|
36
|
+
return Tioga::FigureConstants.const_get(str)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# This class is just a means to make an instance of Array stand out
|
|
41
|
+
class SpecialArray < Array
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
# The MovingArray class provides a way to scroll through an array,
|
|
46
|
+
# such as a set of colors, markers, linstyles, and so on. The class
|
|
47
|
+
# provides a way to set the value directly as well, and to forget it.
|
|
48
|
+
class MovingArray
|
|
49
|
+
include Tioga
|
|
50
|
+
|
|
51
|
+
def initialize(sets, current = nil, startpoint = 0)
|
|
52
|
+
@sets = sets
|
|
53
|
+
|
|
54
|
+
# Pick the first key as a default
|
|
55
|
+
current = @sets.keys.first unless current
|
|
56
|
+
choose_current_set(current)
|
|
57
|
+
|
|
58
|
+
@current_point = startpoint # the current point in the current set
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def value
|
|
62
|
+
@current_point += 1
|
|
63
|
+
if @current_point >= @current_set.length
|
|
64
|
+
@current_point %= @current_set.length
|
|
65
|
+
end
|
|
66
|
+
return @current_set[@current_point - 1]
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Chooses the current set. If the name refers to an existing set,
|
|
70
|
+
# this one is chosen. If not, and if the name looks like a gradient
|
|
71
|
+
# specification, we interpret it as such. In any other case,
|
|
72
|
+
# the chosen set is the set containing only the argument. This way,
|
|
73
|
+
# to get a set containing a single color, you can use
|
|
74
|
+
#
|
|
75
|
+
# choose_current_set(Pink)
|
|
76
|
+
def choose_current_set(name)
|
|
77
|
+
@current_set_name = name
|
|
78
|
+
if @sets.key?(name)
|
|
79
|
+
@current_set_name = name
|
|
80
|
+
@current_set = @sets[name]
|
|
81
|
+
elsif name =~ /gradient:(.+)--(.+),(\d+)/
|
|
82
|
+
# Special case for color gradients
|
|
83
|
+
s = CTioga.get_tioga_color($1)
|
|
84
|
+
e = CTioga.get_tioga_color($2)
|
|
85
|
+
nb = $3.to_i
|
|
86
|
+
fact = if nb > 1
|
|
87
|
+
1.0/(nb - 1) # The famous off-by one...
|
|
88
|
+
else
|
|
89
|
+
warn "Incorrect gradient number: #{nb}"
|
|
90
|
+
1.0
|
|
91
|
+
end
|
|
92
|
+
array = []
|
|
93
|
+
nb.times do |i|
|
|
94
|
+
a = []
|
|
95
|
+
f = i * fact
|
|
96
|
+
e.each_index do |c|
|
|
97
|
+
a << s[c] * (1 - f) + e[c] * f
|
|
98
|
+
end
|
|
99
|
+
array << a
|
|
100
|
+
end
|
|
101
|
+
@current_set = array
|
|
102
|
+
else
|
|
103
|
+
if name.is_a? SpecialArray
|
|
104
|
+
@current_set = name
|
|
105
|
+
else
|
|
106
|
+
@current_set = [name]
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Returns the available sets.
|
|
112
|
+
def available_sets
|
|
113
|
+
return @sets.keys
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Tells whether the given name is a valid set. Takes care of the special
|
|
117
|
+
# case of gradients.
|
|
118
|
+
#
|
|
119
|
+
# TODO: this looks hackish, somehow, but I don't see a way to make
|
|
120
|
+
# that less so...
|
|
121
|
+
def valid_set?(name)
|
|
122
|
+
if name =~ /^gradient:/
|
|
123
|
+
return true
|
|
124
|
+
else
|
|
125
|
+
return @sets.key? name
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
end
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
# partition.rb: segment partitioning functions
|
|
2
|
+
# Copyright (c) 2008 by Vincent Fourmond
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
# This program is free software; you can redistribute it and/or modify
|
|
6
|
+
# it under the terms of the GNU General Public License as published by
|
|
7
|
+
# the Free Software Foundation; either version 2 of the License, or
|
|
8
|
+
# (at your option) any later version.
|
|
9
|
+
|
|
10
|
+
# This program is distributed in the hope that it will be useful,
|
|
11
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
# GNU General Public License for more details (in the COPYING file).
|
|
14
|
+
|
|
15
|
+
require 'CTioga/utils'
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
module CTioga
|
|
19
|
+
|
|
20
|
+
Version::register_svn_info('$Revision: 792 $', '$Date: 2008-05-23 00:19:31 +0200 (Fri, 23 May 2008) $')
|
|
21
|
+
|
|
22
|
+
# A module for small convenience functions.
|
|
23
|
+
module Utils
|
|
24
|
+
|
|
25
|
+
# The following code is stolen from SciYAG/lib/utils.rb,
|
|
26
|
+
# and is used to partition segments in natural-looking
|
|
27
|
+
# subdivisions.
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
# Our natural way to split decades
|
|
31
|
+
NaturalDistances = Dobjects::Dvector[1, 2, 2.5, 5, 10]
|
|
32
|
+
|
|
33
|
+
# Attempts to partition the given segment in at most _nb_
|
|
34
|
+
# segments of equal size. The segments don't necessarily start on
|
|
35
|
+
# the edge of the original segment
|
|
36
|
+
def self.partition_segment(min, max, nb)
|
|
37
|
+
if min > max
|
|
38
|
+
return partition_segment(max, min, nb)
|
|
39
|
+
elsif min.nan? or max.nan?
|
|
40
|
+
return [0] * nb
|
|
41
|
+
elsif min == max
|
|
42
|
+
return self.partition_segment(min * 0.7, min * 1.3, nb)
|
|
43
|
+
end
|
|
44
|
+
distance = max - min
|
|
45
|
+
min_distance = distance/(nb + 1.0) # Why + 1.0 ? To account
|
|
46
|
+
# for the space that could be left on the side.
|
|
47
|
+
|
|
48
|
+
# The order of magnitude of the distance:
|
|
49
|
+
order = min_distance.log10.floor
|
|
50
|
+
|
|
51
|
+
# A distance which is within [1, 10 [ (but the latter is never reached.
|
|
52
|
+
normalized_distance = min_distance * 10**(-order)
|
|
53
|
+
final_distance = NaturalDistances.min_gt(normalized_distance) *
|
|
54
|
+
10**(order)
|
|
55
|
+
# puts "Distance: #{distance} in #{nb} : #{normalized_distance} #{final_distance}"
|
|
56
|
+
|
|
57
|
+
# We're getting closer now: we found the natural distance between
|
|
58
|
+
# ticks.
|
|
59
|
+
|
|
60
|
+
start = (min/final_distance).ceil * final_distance
|
|
61
|
+
retval = []
|
|
62
|
+
val = start
|
|
63
|
+
while val <= max
|
|
64
|
+
retval << val
|
|
65
|
+
# I use this to avoid potential cumulative addition
|
|
66
|
+
# rounding errors
|
|
67
|
+
val = start + final_distance * retval.size
|
|
68
|
+
end
|
|
69
|
+
return retval
|
|
70
|
+
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Our natural way to split decades - except that all successive
|
|
74
|
+
# element now divide each other.
|
|
75
|
+
NaturalDistancesNonLinear = Dobjects::Dvector[1, 2.5, 5, 10]
|
|
76
|
+
|
|
77
|
+
# A class that handles a "natural distance". Create it by giving
|
|
78
|
+
# the initial distance. You can then get its value, lower it,
|
|
79
|
+
# increase it, find the first/last element of an interval that is
|
|
80
|
+
# a multiple if it
|
|
81
|
+
class NaturalDistance
|
|
82
|
+
|
|
83
|
+
# Creates the biggest 'natural distance' that is smaller
|
|
84
|
+
# than _distance_
|
|
85
|
+
def initialize(distance)
|
|
86
|
+
@order = distance.log10.floor
|
|
87
|
+
normalized = distance * 10**(-@order)
|
|
88
|
+
@index = 0
|
|
89
|
+
for dist in NaturalDistances
|
|
90
|
+
if dist > normalized
|
|
91
|
+
break
|
|
92
|
+
else
|
|
93
|
+
@index += 1
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Returns the actual value of the distance
|
|
99
|
+
def value
|
|
100
|
+
return NaturalDistances[@index] * 10**(@order)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Goes to the natural distance immediately under the current one
|
|
104
|
+
def decrease
|
|
105
|
+
if @index > 0
|
|
106
|
+
@index -= 1
|
|
107
|
+
else
|
|
108
|
+
@index = NaturalDistances.size - 1
|
|
109
|
+
@order -= 1
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Goes to the natural distance immediately under the current one
|
|
114
|
+
def increase
|
|
115
|
+
if @index < NaturalDistances.size - 1
|
|
116
|
+
@index += 1
|
|
117
|
+
else
|
|
118
|
+
@index = 0
|
|
119
|
+
@order += 1
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Find the minimum element inside the given interval
|
|
124
|
+
# that is a multiple of this distance
|
|
125
|
+
def find_minimum(x1, x2)
|
|
126
|
+
x1,x2 = x2, x1 if x1 > x2
|
|
127
|
+
|
|
128
|
+
dist = value
|
|
129
|
+
v = (x1/dist).ceil * dist
|
|
130
|
+
if v <= x2
|
|
131
|
+
return v
|
|
132
|
+
else
|
|
133
|
+
return false
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# Returns the value of the next decade (ascending)
|
|
138
|
+
def next_decade(x)
|
|
139
|
+
decade = 10.**(@order + 1)
|
|
140
|
+
if @index == 0 # We stop at 0.5 if the increase is 0.5
|
|
141
|
+
decade *= 0.5
|
|
142
|
+
end
|
|
143
|
+
nb = Float((x/decade).ceil)
|
|
144
|
+
if nb == x/decade
|
|
145
|
+
return (nb + 1)*decade
|
|
146
|
+
else
|
|
147
|
+
return nb * decade
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# Returns the number of elements to #next_decade
|
|
152
|
+
def nb_to_next_decade(x)
|
|
153
|
+
dist = value
|
|
154
|
+
dec = next_decade(x)
|
|
155
|
+
return dec/dist - (x/dist).ceil + 1
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# Returns a list of all the values corresponding to
|
|
159
|
+
# the distance
|
|
160
|
+
def to_next_decade(x)
|
|
161
|
+
next_decade = next_decade(x)
|
|
162
|
+
dist = value
|
|
163
|
+
start = (x/dist).ceil * dist
|
|
164
|
+
retval = []
|
|
165
|
+
while start + retval.size * dist <= next_decade
|
|
166
|
+
retval << start + retval.size * dist
|
|
167
|
+
end
|
|
168
|
+
return retval
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
# Attempts to partition the segment image of _min_, _max_
|
|
175
|
+
# by the Proc object _to_ into at most _nb_ elements. The
|
|
176
|
+
# reverse of the transformation, _from_, has to be provided.
|
|
177
|
+
def self.partition_nonlinear(to, from, x1, x2, nb)
|
|
178
|
+
x1, x2 = x2, x1 if x1 > x2
|
|
179
|
+
if x1.nan? or x2.nan?
|
|
180
|
+
return [0] * nb
|
|
181
|
+
elsif x1 == x2 # Nothing to do
|
|
182
|
+
return self.partition_segment(x1 * 0.7, x2 * 1.3, nb)
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
xdist = x2 - x1
|
|
186
|
+
xdist_min = xdist/(nb + 1.0) # Why + 1.0 ? To account
|
|
187
|
+
# for the space that could be left on the side.
|
|
188
|
+
|
|
189
|
+
y1 = to.call(x1)
|
|
190
|
+
y2 = to.call(x2)
|
|
191
|
+
|
|
192
|
+
# Make sure y1 < y2
|
|
193
|
+
y1, y2 = y2, y1 if y1 > y2
|
|
194
|
+
|
|
195
|
+
# We first need to check if the linear partitioning of
|
|
196
|
+
# the target segment could be enough:
|
|
197
|
+
|
|
198
|
+
candidate = self.partition_segment(y1, y2, nb)
|
|
199
|
+
candidate_real = candidate.map(&from)
|
|
200
|
+
|
|
201
|
+
# We inspect the segment: if one of the length deviates from the
|
|
202
|
+
# average expected by more than 25%, we drop it
|
|
203
|
+
|
|
204
|
+
length = []
|
|
205
|
+
p candidate_real, xdist_min
|
|
206
|
+
0.upto(candidate.size - 2) do |i|
|
|
207
|
+
length << (candidate_real[i+1] - candidate_real[i]).abs/(xdist_min)
|
|
208
|
+
end
|
|
209
|
+
p length
|
|
210
|
+
# If everything stays within 25% off, we keep that
|
|
211
|
+
if length.min > 0.75 and length.max < 1.7
|
|
212
|
+
return candidate
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
# We start with a geometric measure of the distance, that
|
|
217
|
+
# will most likely scale better:
|
|
218
|
+
ydist = y1 * (y2/y1).abs ** (1/(nb + 1.0))
|
|
219
|
+
|
|
220
|
+
cur_dist = NaturalDistance.new(ydist)
|
|
221
|
+
|
|
222
|
+
retval = []
|
|
223
|
+
|
|
224
|
+
cur_y = y1
|
|
225
|
+
# This flag is necessary to avoid infinite loops
|
|
226
|
+
last_was_decrease = false
|
|
227
|
+
|
|
228
|
+
distance_unchanged = 0
|
|
229
|
+
last_real_distance = false
|
|
230
|
+
while cur_y < y2
|
|
231
|
+
candidates = cur_dist.to_next_decade(cur_y)
|
|
232
|
+
# We now evaluate the distance in real
|
|
233
|
+
real_distance = (from.call(cur_y) - from.call(candidates.last)).abs/
|
|
234
|
+
candidates.size
|
|
235
|
+
if last_real_distance && (real_distance == last_real_distance)
|
|
236
|
+
distance_unchanged += 1
|
|
237
|
+
else
|
|
238
|
+
distance_unchanged = 0
|
|
239
|
+
end
|
|
240
|
+
# p [:cur_y=, cur_y, :y2=, y2, :real_distance, real_distance,
|
|
241
|
+
# :distance=, cur_dist, :xdist_min, xdist_min,
|
|
242
|
+
# :candidates=, *candidates]
|
|
243
|
+
|
|
244
|
+
if (real_distance > 1.25 * xdist_min) &&
|
|
245
|
+
(distance_unchanged < 3)
|
|
246
|
+
cur_dist.decrease
|
|
247
|
+
last_was_decrease = true
|
|
248
|
+
elsif real_distance < 0.75 * xdist_min &&
|
|
249
|
+
!last_was_decrease && (distance_unchanged < 3) &&
|
|
250
|
+
candidates.last <= 10 * y2
|
|
251
|
+
cur_dist.increase
|
|
252
|
+
last_was_decrease = false
|
|
253
|
+
else
|
|
254
|
+
retval += candidates
|
|
255
|
+
cur_y = candidates.last
|
|
256
|
+
last_was_decrease = false
|
|
257
|
+
end
|
|
258
|
+
last_real_distance = real_distance
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
# We need to select them so
|
|
262
|
+
return retval.select do |y|
|
|
263
|
+
y >= y1 and y <= y2
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
end
|