FuncSketch
Loading...
Searching...
No Matches
plotter.cpp
Go to the documentation of this file.
1/*
2 * Copyright 2026 MusicScience37 (Kenta Kabashima)
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
21
22#include <algorithm>
23#include <cmath>
24#include <cstddef>
25
26#include <fmt/format.h>
27#include <opencv2/imgproc.hpp>
28
33
34namespace func_sketch::plotter {
35
36namespace {
37
46[[nodiscard]] cv::Point adjust_text_position(const cv::Point& position,
47 const cv::Size& text_size, const cv::Size& image_size) {
48 cv::Point adjusted_position = position;
49
50 adjusted_position.x = std::max(adjusted_position.x, 0);
51 adjusted_position.x =
52 std::min(adjusted_position.x, image_size.width - text_size.width);
53
54 adjusted_position.y = std::max(adjusted_position.y, 0);
55 adjusted_position.y =
56 std::min(adjusted_position.y, image_size.height - text_size.height);
57
58 return adjusted_position;
59}
60
70void update_axis_ticks(const PlotRange& range, const cv::MatSize& image_size,
71 const PlotConfig& config, AxisTicks& x_axis_ticks,
72 AxisTicks& y_axis_ticks) {
73 const std::size_t approx_num_ticks_x = std::max(static_cast<std::size_t>(1),
74 static_cast<std::size_t>(image_size[1]) /
75 config.num_pixels_per_tick_in_x_axis());
76 const std::size_t approx_num_ticks_y = std::max(static_cast<std::size_t>(1),
77 static_cast<std::size_t>(image_size[0]) /
78 config.num_pixels_per_tick_in_y_axis());
79
80 generate_axis_ticks(range.x_range(), approx_num_ticks_x, x_axis_ticks);
81 generate_axis_ticks(range.y_range(), approx_num_ticks_y, y_axis_ticks);
82}
83
85constexpr int plot_text_font_face = cv::FONT_HERSHEY_SIMPLEX;
86
88constexpr int plot_text_thickness = 1;
89
97[[nodiscard]] int compute_required_width_for_y_axis_tick_labels(
98 const AxisTicks& y_axis_ticks, const PlotConfig& config) {
99 const int font_size = config.tick_label_font_size();
100 const double font_scale =
101 cv::getFontScaleFromHeight(plot_text_font_face, font_size);
102 int required_width = 0;
103 for (const auto& text : y_axis_ticks.strings) {
104 const cv::Size text_size = cv::getTextSize(text, plot_text_font_face,
105 font_scale, plot_text_thickness, nullptr);
106 required_width = std::max(required_width, text_size.width);
107 }
108 return required_width;
109}
110
111} // namespace
112
113// NOLINTNEXTLINE(*-pass-by-value): Wrong warning for small objects.
116
118 range_ = value;
119 return *this;
120}
121
123 config_ = value;
124 return *this;
125}
126
128 const auto size = image.size;
129 update_axis_ticks(range_, size, config_, x_axis_ticks_, y_axis_ticks_);
130 left_margin_ = config_.left_margin();
131 const int required_width_for_y_axis_tick_labels =
132 compute_required_width_for_y_axis_tick_labels(y_axis_ticks_, config_);
133 const int margin_for_y_axis_tick_labels = config_.tick_label_font_size();
134 left_margin_ = std::max(left_margin_,
135 required_width_for_y_axis_tick_labels + margin_for_y_axis_tick_labels);
136
137 // Background.
138 const auto color = convert_color(config_.background_color());
139 image = color;
140
141 write_grid_lines(image);
142 write_x_axis(image);
143 write_y_axis(image);
144}
145
147 const std::vector<Point>& samples, const RGBColor& color, Image& image) {
148 const auto size = image.size;
149
150 const auto cv_color = convert_color(color);
151 const int line_width = config_.curve_line_width();
152
153 const std::size_t num_samples = samples.size();
154 if (num_samples < 2) {
155 return;
156 }
157 for (std::size_t i = 0; i < num_samples - 1; ++i) {
158 Point start_xy = samples[i];
159 Point end_xy = samples[i + 1];
160
161 const bool is_start_in_range = range_.contains(start_xy);
162 const bool is_end_in_range = range_.contains(end_xy);
163 if (!is_start_in_range && !is_end_in_range) {
164 // In this case, the range of the line segment may contain a
165 // singularity of the function.
166 continue;
167 }
168
169 if (std::isnan(start_xy.x) || std::isnan(start_xy.y) ||
170 std::isnan(end_xy.x) || std::isnan(end_xy.y)) {
171 // NaN cannot be fixed.
172 continue;
173 }
174
175 if (!is_start_in_range) {
176 if (!try_clamp_infinity(start_xy, range_)) {
177 continue;
178 }
179 start_xy =
180 compute_intersection_with_range(end_xy, start_xy, range_);
181 }
182 if (!is_end_in_range) {
183 if (!try_clamp_infinity(end_xy, range_)) {
184 continue;
185 }
186 end_xy = compute_intersection_with_range(start_xy, end_xy, range_);
187 }
188
189 write_line(image, start_xy, end_xy, cv_color, line_width, range_,
191 }
192}
193
195 const auto size = image.size;
196
197 // vertical lines.
198 for (const Real x_value : x_axis_ticks_.values) {
199 const int line_width = (x_value == 0.0) ? config_.zero_line_width()
200 : config_.grid_line_width();
201 write_line(image, Point{.x = x_value, .y = range_.y_range().first},
202 Point{.x = x_value, .y = range_.y_range().second},
203 convert_color(config_.grid_color()), line_width, range_, config_,
205 }
206 // horizontal lines.
207 for (const Real y_value : y_axis_ticks_.values) {
208 const int line_width = (y_value == 0.0) ? config_.zero_line_width()
209 : config_.grid_line_width();
210 write_line(image, Point{.x = range_.x_range().first, .y = y_value},
211 Point{.x = range_.x_range().second, .y = y_value},
212 convert_color(config_.grid_color()), line_width, range_, config_,
214 }
215}
216
218 const auto size = image.size;
219
220 const auto color = convert_color(config_.axes_color());
221
222 // Draw the x-axis at the bottom in the plot.
223 const Real y_value = range_.y_range().first;
224
225 write_line(image, Point{.x = range_.x_range().first, .y = y_value},
226 Point{.x = range_.x_range().second, .y = y_value}, color,
227 config_.axes_line_width(), range_, config_, left_margin_);
228
229 const int font_size = config_.tick_label_font_size();
230 const double font_scale =
231 cv::getFontScaleFromHeight(plot_text_font_face, font_size);
232
233 assert(x_axis_ticks_.values.size() == x_axis_ticks_.strings.size());
234 for (std::size_t i = 0; i < x_axis_ticks_.values.size(); ++i) {
235 const Real x_value = x_axis_ticks_.values[i];
236
237 const auto text = x_axis_ticks_.strings[i];
238 const cv::Size text_size = cv::getTextSize(text, plot_text_font_face,
239 font_scale, plot_text_thickness, nullptr);
240
241 const auto base_position =
242 convert_position(Point{.x = x_value, .y = y_value}, range_, config_,
243 left_margin_, size);
244 const int tick_margin = font_size / 2;
245 auto top_left_position =
246 cv::Point(base_position.x - text_size.width / 2,
247 base_position.y + tick_margin + text_size.height);
248 top_left_position = adjust_text_position(
249 top_left_position, text_size, cv::Size(size[1], size[0]));
250
251 cv::putText(image, text, top_left_position, plot_text_font_face,
252 font_scale, color, plot_text_thickness, cv::LINE_AA);
253 }
254}
255
257 const auto size = image.size;
258
259 const auto color = convert_color(config_.axes_color());
260
261 // Draw the y-axis at the left in the plot.
262 const Real x_value = range_.x_range().first;
263
264 write_line(image, Point{.x = x_value, .y = range_.y_range().first},
265 Point{.x = x_value, .y = range_.y_range().second}, color,
266 config_.axes_line_width(), range_, config_, left_margin_);
267
268 const int font_size = config_.tick_label_font_size();
269 const double font_scale =
270 cv::getFontScaleFromHeight(plot_text_font_face, font_size);
271
272 assert(y_axis_ticks_.values.size() == y_axis_ticks_.strings.size());
273 for (std::size_t i = 0; i < y_axis_ticks_.values.size(); ++i) {
274 const Real y_value = y_axis_ticks_.values[i];
275
276 const auto text = y_axis_ticks_.strings[i];
277 const cv::Size text_size = cv::getTextSize(text, plot_text_font_face,
278 font_scale, plot_text_thickness, nullptr);
279
280 const auto base_position =
281 convert_position(Point{.x = x_value, .y = y_value}, range_, config_,
282 left_margin_, size);
283 const int tick_margin = font_size / 2;
284 cv::Point top_left_position;
285 top_left_position =
286 cv::Point(base_position.x - tick_margin - text_size.width,
287 base_position.y + text_size.height / 2);
288 top_left_position = adjust_text_position(
289 top_left_position, text_size, cv::Size(size[1], size[0]));
290
291 cv::putText(image, text, top_left_position, plot_text_font_face,
292 font_scale, color, plot_text_thickness, cv::LINE_AA);
293 }
294}
295
296} // namespace func_sketch::plotter
Definition of AxisTicks class.
void generate_axis_ticks(const std::pair< Real, Real > &range, std::size_t approx_num_ticks, AxisTicks &ticks)
Generate ticks of axes.
Class of configurations of a plot.
Definition plot_config.h:97
Class of a range of a plot.
Definition plot_range.h:35
Plotter & config(const PlotConfig &value)
Set the configuration of plots.
Definition plotter.cpp:122
void write_curve(const std::vector< Point > &samples, const RGBColor &color, Image &image)
Write a curve on plots.
Definition plotter.cpp:146
AxisTicks x_axis_ticks_
Ticks of the x-axis.
Definition plotter.h:121
void write_y_axis(Image &image)
Write y axis.
Definition plotter.cpp:256
void write_x_axis(Image &image)
Write x axis.
Definition plotter.cpp:217
PlotConfig config_
Configuration of plots.
Definition plotter.h:112
PlotRange range_
Range of plots.
Definition plotter.h:109
Plotter & range(const PlotRange &value)
Set the range of plots.
Definition plotter.cpp:117
int left_margin_
Left margin of plots in pixels. (This value is used over the value in config because tuning of the le...
Definition plotter.h:118
void write_grid_lines(Image &image)
Write grid lines of plots.
Definition plotter.cpp:194
Plotter(const PlotRange &range, const PlotConfig &config)
Constructor.
Definition plotter.cpp:114
AxisTicks y_axis_ticks_
Ticks of the y-axis.
Definition plotter.h:124
void write_background(Image &image)
Write background of plots.
Definition plotter.cpp:127
Definitions of common types.
double Real
Type of real numbers in this project.
cv::Mat Image
Image type.
Definition image.h:29
Definition of Plotter class.
Declaration of functions for internal implementation of plotting.
Point compute_intersection_with_range(const Point &point_in_range, const Point &point_out_of_range, const PlotRange &range)
Compute the intersection of a line segment with the boundary of the plot range.
cv::Point convert_position(const Point &position, const PlotRange &range, const PlotConfig &config, int left_margin, const cv::MatSize &size)
Convert a position from plot coordinates to image coordinates.
cv::Scalar convert_color(const RGBColor &color)
Convert a color from RGBColor to cv::Scalar.
void write_line(Image &image, const Point &start_point, const Point &end_point, const cv::Scalar &color, int line_width, const PlotRange &range, const PlotConfig &config, int left_margin)
Write a line on an image.
bool try_clamp_infinity(Point &point, const PlotRange &range)
Try to clamp a point with infinity to the range.
Definition of Point struct.
Struct to represent ticks of axes.
Definition axis_ticks.h:34
Struct of a point.
Definition point.h:29
double y
Y coordinate.
Definition point.h:34
double x
X coordinate.
Definition point.h:31
Struct of RGB color.
Definition rgb_color.h:31