Class: Plot::Plotter

Inherits:
Object
  • Object
show all
Defined in:
lib/plot/plot.rb

Instance Method Summary collapse

Constructor Details

#initialize(log_file, layer: nil, dark: false) ⇒ Plotter

Returns a new instance of Plotter.



42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# File 'lib/plot/plot.rb', line 42

def initialize(log_file, layer: nil, dark: false)
  @dark = dark
  begin
    if layer
      patterns = (0...layer).map { |i| File.join(['.'] + ['*'] * i, log_file) }
      files = patterns.flat_map { |pat| Dir.glob(pat, base: '.') }.select { |f| File.exist?(f) }
      raise "No log files found for pattern(s): #{patterns.join(', ')}" if files.empty?
      @log = files.flat_map { |f| YAML.load(File.read(f)) }
    else
      unless File.exist?(log_file)
        raise "Log file not found: #{log_file}"
      end
      @log = YAML.load(File.read(log_file))
    end
  rescue => e
    puts "[Error] #{e.message}".colorize(:red)
    exit 1
  end
end

Instance Method Details

#generate_py_code(x_label:, y_label:, title:, x_data:, y_data:, x_fmt:, y_lim: nil, x_date_fmt: nil) ⇒ Object

共通のpy_code生成メソッド



69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
# File 'lib/plot/plot.rb', line 69

def generate_py_code(x_label:, y_label:, title:, x_data:, y_data:, x_fmt:, y_lim: nil, x_date_fmt: nil)
  y_lim_code = y_lim ? "plt.ylim(#{y_lim[0]}, #{y_lim[1]})" : ""
  date_fmt_import = x_date_fmt ? "from matplotlib.dates import DateFormatter" : ""
  date_fmt_setter = x_date_fmt ? "plt.gca().xaxis.set_major_formatter(DateFormatter('#{x_date_fmt}'))" : ""
  dark_theme_code = @dark ? "plt.style.use('dark_background')" : ""
  <<~PYTHON
    import matplotlib.pyplot as plt
    from datetime import datetime
    from matplotlib.ticker import MaxNLocator
    #{date_fmt_import}
    #{dark_theme_code}

    x_data = #{x_data}
    y_data = #{y_data}

    x = [datetime.strptime(val, "#{x_fmt}") for val in x_data]
    y = y_data

    plt.figure(figsize=(6,5))  # 横幅を半分に
    plt.plot(x, y, marker='o')
    plt.gca().xaxis.set_major_locator(MaxNLocator(nbins=6))  # x軸tickを最大6個に制限
    #{date_fmt_setter}
    plt.title('#{title}')
    plt.xlabel('#{x_label}')
    plt.ylabel('#{y_label}')
    #{y_lim_code}
    plt.grid(True)
    plt.tight_layout()
    plt.show()
  PYTHON
end

#generic_plot(x_label:, y_label:, title:, x_fmt:, y_lim: nil, x_date_fmt: nil, &extractor) ⇒ Object



101
102
103
104
105
106
107
108
109
110
111
112
113
114
# File 'lib/plot/plot.rb', line 101

def generic_plot(x_label:, y_label:, title:, x_fmt:, y_lim: nil, x_date_fmt: nil, &extractor)
  x_data, y_data = extractor.call(@log)
  py_code = generate_py_code(
    x_label: x_label,
    y_label: y_label,
    title: title,
    x_data: x_data,
    y_data: y_data,
    x_fmt: x_fmt,
    y_lim: y_lim,
    x_date_fmt: x_date_fmt
  )
  run_python_plot(py_code)
end

#plot_check_cumulative_per_minuteObject

例えば cumulative の場合



165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
# File 'lib/plot/plot.rb', line 165

def plot_check_cumulative_per_minute
  generic_plot(
    x_label: 'Time (minute)',
    y_label: 'Cumulative Practice Count',
    title: 'Dependence of Cumulative Practice Time',
    x_fmt: "%Y-%m-%d %H:%M",
    x_date_fmt: "%m-%d"
  ) do |log|
    counts = Hash.new(0)
    log.each do |entry|
      timestamp = entry.is_a?(Array) ? entry[0] : entry.to_s
      minute_str = timestamp[0,16]
      counts[minute_str] += 1
    end
    sorted_minutes = counts.keys.sort
    sorted_counts = sorted_minutes.map { |m| counts[m] }
    cumulative_counts = []
    sum = 0
    sorted_counts.each do |c|
      sum += c
      cumulative_counts << sum
    end
    # 時間でソート(sorted_minutesは既にsort済みだが念のため)
    [sorted_minutes, cumulative_counts]
  end
end

#plot_score_logObject



139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
# File 'lib/plot/plot.rb', line 139

def plot_score_log
  generic_plot(
    x_label: 'Time',
    y_label: 'Score (%)',
    title: 'Score Percentage Over Time',
    x_fmt: "%Y-%m-%d %H:%M:%S",
    y_lim: [0, 100]
  ) do |log|
    times = []
    scores = []
    log.each do |entry|
      timestamp = entry[:date]
      score_str = entry[:score].to_s
      num, denom = score_str.split('/').map(&:to_f)
      percent = (num / denom * 100).round(1)
      times << timestamp
      scores << percent
    end
    # 時間でソート
    sorted = times.zip(scores).sort_by { |t, _| t }
    sorted_times, sorted_scores = sorted.transpose
    [sorted_times || [], sorted_scores || []]
  end
end

#plot_word_size_logObject



116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
# File 'lib/plot/plot.rb', line 116

def plot_word_size_log
  generic_plot(
    x_label: 'Time',
    y_label: 'Word Size',
    title: 'Word Size Over Time',
    x_fmt: "%Y-%m-%d %H:%M:%S"
  ) do |log|
    times = []
    sizes = []
    log.each do |entry|
      timestamp = entry[:date]
      size = entry[:size]
      next if timestamp.nil? || size.nil?
      times << timestamp
      sizes << size.to_i
    end
    # 時間でソート
    sorted = times.zip(sizes).sort_by { |t, _| t }
    sorted_times, sorted_sizes = sorted.transpose
    [sorted_times || [], sorted_sizes || []]
  end
end

#run_python_plot(py_code) ⇒ Object



62
63
64
65
66
# File 'lib/plot/plot.rb', line 62

def run_python_plot(py_code)
  File.write('tmp_plot.py', py_code)
  system('python3 tmp_plot.py')
  File.delete('tmp_plot.py')
end