// // Created by Alan Freitas on 2020-07-03. // #include #include #include #include #include #include #include #include #include #include // Allow the warnings related to the bundled CImg #ifdef _MSC_VER #pragma warning(push) #pragma warning(disable : 4458) #pragma warning(disable : 4456) #pragma warning(disable : 4702) #endif #include #ifdef _MSC_VER #pragma warning(pop) #endif #ifdef _WIN32 #include #define PCLOSE _pclose #define POPEN _popen #define FILENO _fileno #else #define PCLOSE pclose #define POPEN popen #define FILENO fileno #endif namespace matplot { bool iequals(std::string_view str1, std::string_view str2) { if (str1.size() != str2.size()) { return false; } else { for (size_t i = 0; i < str1.size(); ++i) { if (toupper(str1[i]) != toupper(str2[i])) { return false; } } } return true; } bool is_true(std::string_view str) { return iequals(str, "on") || iequals(str, "true") || iequals(str, "yes"); } bool is_false(std::string_view str) { return iequals(str, "off") || iequals(str, "false") || iequals(str, "no"); } std::string run_and_get_output(const std::string &cmd) { std::unique_ptr pipe(POPEN(cmd.c_str(), "r"), PCLOSE); if (!pipe) { throw std::runtime_error("popen() failed!"); } std::array buffer{}; std::string result; while (fgets(buffer.data(), static_cast(buffer.size()), pipe.get()) != nullptr) { result += buffer.data(); } return result; } std::string escape(std::string_view label) { std::string escaped; escaped.reserve(label.size()); std::regex_replace(std::back_inserter(escaped), label.begin(), label.end(), std::regex("\""), "\\\""); return escaped; } std::vector linspace(double d1, double d2, size_t n) { std::vector x(n); // avoid division by zero double step = n > 1? (d2 - d1) / static_cast(n - 1) : 0.; for (size_t i = 0; i < x.size(); ++i) { x[i] = d1 + static_cast(i) * step; } return x; } std::vector linspace(double d1, double d2) { return linspace(d1, d2, 100); } std::vector logspace(double d1, double d2, size_t n) { return transform(linspace(d1, d2, n), [](double x) { return pow(10, x); }); } std::vector logspace(double d1, double d2) { return logspace(d1, d2, 50); } std::vector iota(double d1, double step, double d2) { vector_1d r; r.reserve(static_cast((d2 - d1) / step)); for (double i = d1; i <= d2; i += step) { r.emplace_back(i); } return r; } std::vector iota(double d1, double d2) { return iota(d1, 1., d2); } std::vector transform(const std::vector &x, std::function fn) { std::vector y(x.size()); for (size_t i = 0; i < y.size(); ++i) { y[i] = fn(x[i]); } return y; } double truncate(double x, double lower_bound, double upper_bound) { if (x <= lower_bound) { return lower_bound; } else if (x >= upper_bound) { return upper_bound; } else { return x; } } std::vector transform(const std::vector &x, const std::vector &y, std::function fn) { std::vector z(x.size()); size_t n = std::min(x.size(), y.size()); for (size_t i = 0; i < n; ++i) { z[i] = fn(x[i], y[i]); } return z; } vector_2d transform(const vector_2d &x, std::function fn) { const size_t n_rows = x.size(); const size_t n_cols = x[0].size(); vector_2d z(n_rows, vector_1d(n_cols, 0.)); for (size_t i = 0; i < n_rows; ++i) { for (size_t j = 0; j < n_cols; ++j) { z[i][j] = fn(x[i][j]); } } return z; } vector_2d transform(const vector_2d &x, const vector_2d &y, std::function fn) { const size_t n_rows = std::min(x.size(), y.size()); const size_t n_cols = std::min(x[0].size(), y[0].size()); vector_2d z(n_rows, vector_1d(n_cols, 0.)); for (size_t i = 0; i < n_rows; ++i) { for (size_t j = 0; j < n_cols; ++j) { z[i][j] = fn(x[i][j], y[i][j]); } } return z; } vector_1d flatten(const vector_2d &x) { vector_1d x_line; x_line.reserve(x.size() * x[0].size()); for (size_t i = 0; i < x.size(); ++i) { x_line.insert(x_line.end(), x[i].begin(), x[i].end()); } return x_line; } double max(const std::vector &x) { if (x.empty()) { return -std::numeric_limits::max(); } else { return *std::max_element(x.begin(), x.end()); } } double min(const std::vector &x) { if (x.empty()) { return std::numeric_limits::max(); } else { return *std::min_element(x.begin(), x.end()); } } double min(const std::vector> &x) { double m = min(x[0]); for (size_t i = 1; i < x.size(); ++i) { m = min(m, min(x[i])); } return m; } double max(const std::vector> &x) { double m = max(x[0]); for (size_t i = 1; i < x.size(); ++i) { m = max(m, max(x[i])); } return m; } std::pair minmax(const std::vector &x) { if (x.empty()) { return std::make_pair(min(x), max(x)); } else { auto [min_it, max_it] = std::minmax_element(x.begin(), x.end()); return std::make_pair(*min_it, *max_it); } } double mean(const std::vector &x) { double sum = 0; for (const double &xi : x) { sum += xi / static_cast(x.size()); } return sum; } double stddev(const std::vector &x) { double m = mean(x); double sum = 0; for (const double &xi : x) { sum += pow(xi - m, 2); } return sqrt(sum / static_cast(x.size() - 1)); } // windows.h library defines a function-like macro with the name of max() #ifdef max // store the predefined macro in a new one #define _TEMP_MACRO_ max // undefine the problamatic macro. #undef max #endif void wait() { #ifdef MATPLOT_BUILD_FOR_DOCUMENTATION_IMAGES return; #endif // http://www.cplusplus.com/forum/beginner/106769/#msg578325 // Clear failed/error states of the stream if they are set std::cin.clear(); // If there are any characters in the input buffer if (std::cin.rdbuf()->in_avail()) { // Throw them away std::cin.ignore(std::numeric_limits::max(), '\n'); } std::cout << "Press ENTER to continue..."; std::cin.ignore(std::numeric_limits::max(), '\n'); } #ifdef _Temp_MACRO_ // restore the max() macro. #define max _TEMP_MACRO_ // undefine the temporary macro. #undef _TEMP_MACRO_ #endif namespace detail { static std::mt19937 &default_generator() { static std::mt19937 g((std::random_device())()); return g; } } // namespace detail double randn(double mean, double stddev) { std::normal_distribution d(mean, stddev); return d(detail::default_generator()); } std::vector randn(size_t n, double mean, double stddev) { std::vector x(n); std::generate(x.begin(), x.end(), [&] { return randn(mean, stddev); }); return x; } double rande(double lambda, double multiplier) { std::exponential_distribution d(lambda); return d(detail::default_generator()) * multiplier; } std::vector rande(size_t n, double lambda, double multiplier) { std::vector x(n); std::generate(x.begin(), x.end(), [&] { return rande(lambda, multiplier); }); return x; } double randp(double scale, double shape) { std::exponential_distribution d(shape); return scale * exp(d(detail::default_generator())); } std::vector randp(size_t n, double scale, double shape) { std::vector x(n); std::generate(x.begin(), x.end(), [&] { return randp(scale, shape); }); return x; } int randi(int imin, int imax) { std::uniform_int_distribution d(imin, imax); return d(detail::default_generator()); } std::vector randi(size_t n, int imin, int imax) { std::vector x(n); std::generate(x.begin(), x.end(), [&] { return randi(imin, imax); }); return x; } double rand(double lower_bound, double upper_bound) { std::uniform_real_distribution d(lower_bound, upper_bound); return d(detail::default_generator()); } std::vector rand(size_t n, double lower_bound, double upper_bound) { std::vector x(n); std::generate(x.begin(), x.end(), [&] { return rand(lower_bound, upper_bound); }); return x; } vector_2d rand(size_t rows, size_t cols, double lower_bound, double upper_bound) { vector_2d x(rows); for (size_t i = 0; i < x.size(); ++i) { x[i] = rand(cols, lower_bound, upper_bound); } return x; } std::vector unique(const std::vector &x) { std::set s; for (const auto &v : x) { s.emplace(v); } std::vector u(s.begin(), s.end()); return u; } std::vector reshape(const std::vector> &X) { std::vector x; for (const auto &vec : X) { for (const auto &item : vec) { x.emplace_back(item); } } return x; } std::vector concat(const std::vector &a, const std::vector &b) { std::vector c(a.begin(), a.end()); c.insert(c.end(), b.begin(), b.end()); return c; } std::string fileread(const std::string &filename) { std::ifstream t(filename); if (!t) { throw std::runtime_error("Cannot open the file " + filename); } std::string str((std::istreambuf_iterator(t)), std::istreambuf_iterator()); return str; } double cosd(double degrees) { // 0 -> 0 // 180 -> pi // 360 -> 2pi return cos(pi * degrees / 180); } double sind(double degrees) { // 0 -> 0 // 180 -> pi // 360 -> 2pi return sin(pi * degrees / 180); } std::vector deg2rad(const std::vector °rees) { std::vector rad(degrees.size()); for (size_t i = 0; i < degrees.size(); ++i) { rad[i] = pi * degrees[i] / 180; } return rad; } double deg2rad(double rho) { return rho * 180. / pi; } double round_polar_max(double polar_max) { // we need to round the polar max because gnuplot // does not use the max value for the r-axis. // it uses a round value. If we explicitly set the // max value, the x-axis and y-axis get into conflict // and things get out of scale. // So the only possible solution is to foresee what // value gnuplot will set for the r-axis limit. double decimals = floor(log10(polar_max)); double base = polar_max / pow(10, decimals); if (base > 1. && base < 1.5) { base = 1.5; } else { base = ceil(base); if (base == 7.) { base += 1.0; } else if (base == 9 || base == 10) { base = 1.0; decimals++; } } return base * pow(10, decimals); } double to_positive_radian(double radian) { radian = std::fmod(radian, pi * 2.); while (radian < 0) { // pretty sure this comparison is valid for doubles and floats radian += pi * 2.; } return radian; } double vector_radians(double x, double y) { return to_positive_radian(atan2(y, x)); } double vector_magnitude(double x, double y) { return sqrt(pow(x, 2) + pow(y, 2)); } double vector_radians(std::complex v) { return vector_radians(v.real(), v.imag()); } double vector_magnitude(std::complex v) { return vector_magnitude(v.real(), v.imag()); } std::pair elliptic2cart(double theta, double rho_w, double rho_h) { return std::make_pair(rho_w * cos(theta), rho_h * sin(theta)); } std::pair elliptic2cart(vector_1d theta, vector_1d rho_w, vector_1d rho_h) { std::pair r; r.first.resize(theta.size()); r.second.resize(theta.size()); for (size_t i = 0; i < theta.size(); ++i) { std::tie(r.first[i], r.second[i]) = elliptic2cart(theta[i], rho_w[i], rho_h[i]); } return r; } std::pair elliptic2cart(vector_1d theta, double rho_w, double rho_h) { std::pair r; r.first.resize(theta.size()); r.second.resize(theta.size()); for (size_t i = 0; i < theta.size(); ++i) { std::tie(r.first[i], r.second[i]) = elliptic2cart(theta[i], rho_w, rho_h); } return r; } std::pair pol2cart(double theta, double rho) { return std::make_pair(rho * cos(theta), rho * sin(theta)); } std::pair pol2cart(vector_1d theta, vector_1d rho) { return std::make_pair( transform( theta, rho, [](double theta, double rho) { return rho * cos(theta); }), transform(theta, rho, [](double theta, double rho) { return rho * sin(theta); })); } std::pair pol2cart(vector_1d theta, double rho) { return std::make_pair( transform(theta, [&](double theta) { return rho * cos(theta); }), transform(theta, [&](double theta) { return rho * sin(theta); })); } std::pair meshgrid(const vector_1d &x, const vector_1d &y) { vector_2d X(y.size(), std::vector(x.size(), 0)); vector_2d Y(y.size(), std::vector(x.size(), 0)); for (size_t i = 0; i < y.size(); ++i) { for (size_t j = 0; j < x.size(); ++j) { X[i][j] = x[j]; Y[i][j] = y[i]; } } return std::make_pair(X, Y); } std::pair meshgrid(const vector_1d &x_and_y) { return meshgrid(x_and_y, x_and_y); } vector_1d gradient(const vector_1d &z, double spacing) { const size_t n = z.size(); vector_1d d(n); d[0] = (z[1] - z[0]) / spacing; d[n - 1] = (z[n - 1] - z[n - 2]) / spacing; for (size_t i = 1; i < n - 1; ++i) { d[i] = ((z[i] - z[i - 1]) + (z[i + 1] - z[i])) / spacing / 2.; } return d; } std::pair gradient(const vector_2d &z, double spacing_x, double spacing_y) { const size_t n1 = z.size(); const size_t n2 = z[0].size(); vector_2d dx(n1, vector_1d(n2, 0.)); vector_2d dy(n1, vector_1d(n2, 0.)); for (size_t row = 0; row < n1; ++row) { dx[row][0] = (z[row][1] - z[row][0]) / spacing_x; dx[row][n2 - 1] = (z[row][n2 - 1] - z[row][n2 - 2]) / spacing_x; for (size_t col = 1; col < n2 - 1; ++col) { dx[row][col] = ((z[row][col] - z[row][col - 1]) + (z[row][col + 1] - z[row][col])) / spacing_x / 2.; } } for (size_t col = 0; col < n2; ++col) { dy[0][col] = (z[1][col] - z[0][col]) / spacing_y; dy[n1 - 1][col] = (z[n1 - 1][col] - z[n1 - 2][col]) / spacing_y; for (size_t row = 1; row < n1 - 1; ++row) { dy[row][col] = ((z[row][col] - z[row - 1][col]) + (z[row + 1][col] - z[row][col])) / spacing_y / 2.; } } return std::make_pair(dx, dy); } std::pair gradient(const vector_2d &z, double spacing) { return gradient(z, spacing, spacing); } double peaks(double x, double y) { return 3. * pow(1. - x, 2.) * exp(-pow(x, 2.) - pow(y + 1., 2.)) - 10. * (x / 5. - pow(x, 3.) - pow(y, 5.)) * exp(-pow(x, 2.) - pow(y, 2.)) - 1. / 3. * exp(-pow(x + 1., 2.) - pow(y, 2.)); } vector_2d peaks(const vector_2d &X, const vector_2d &Y) { const size_t n_rows = std::min(X.size(), Y.size()); const size_t n_cols = std::min(X[0].size(), Y[0].size()); vector_2d Z(n_rows, vector_1d(n_cols, 0.)); for (size_t i = 0; i < n_rows; ++i) { for (size_t j = 0; j < n_cols; ++j) { Z[i][j] = peaks(X[i][j], Y[i][j]); } } return Z; } std::tuple peaks(size_t N) { std::vector x = linspace(-3., +3., N); std::vector y = linspace(-3., +3., N); auto [X, Y] = meshgrid(x, y); return std::make_tuple(X, Y, peaks(X, Y)); } vector_2d zeros(size_t rows, size_t cols) { return vector_2d(rows, vector_1d(cols, 0.)); } vector_2d ones(size_t rows, size_t cols) { return vector_2d(rows, vector_1d(cols, 0.)); } image_channels_t cimg2channels(const cimg_library::CImg &cimg_image) { const int n_channels = cimg_image.spectrum(); const size_t height = cimg_image.height(); const size_t width = cimg_image.width(); image_channels_t img(n_channels, image_channel_t(height, image_row_t(width, 0))); for (int channel = 0; channel < n_channels; ++channel) { for (int i = 0; i < cimg_image.height(); ++i) { for (int j = 0; j < cimg_image.width(); ++j) { img[channel][i][j] = cimg_image.operator()(j, i, 0, channel); } } } return img; } cimg_library::CImg channels2cimg(const image_channels_t &A) { cimg_library::CImg cimg_image( static_cast(A[0].size()), static_cast(A[0][0].size()), 1U, static_cast(A.size())); const int n_channels = static_cast(A.size()); // const size_t height = A[0].size(); // const size_t width = A[0][0].size(); for (int channel = 0; channel < n_channels; ++channel) { for (int i = 0; i < cimg_image.height(); ++i) { for (int j = 0; j < cimg_image.width(); ++j) { cimg_image.operator()(j, i, 0, channel) = A[channel][i][j]; } } } return cimg_image; } image_channels_t imread(const std::string &filename) { try { cimg_library::CImg cimg_image(filename.c_str()); return cimg2channels(cimg_image); } catch (...) { // return empty tuple if we can't open the file return image_channels_t{0}; } } image_channels_t imresize(const image_channels_t &A, size_t height, size_t width, image_interpolation m) { auto cimg_image = channels2cimg(A); int interpolation_type = 0; switch (m) { case image_interpolation::raw: interpolation_type = -1; break; case image_interpolation::additional_space: interpolation_type = 0; break; case image_interpolation::nearest: interpolation_type = 1; break; case image_interpolation::moving_average: interpolation_type = 2; break; case image_interpolation::bilinear: interpolation_type = 3; break; case image_interpolation::grid: interpolation_type = 4; break; case image_interpolation::bicubic: interpolation_type = 5; break; case image_interpolation::lanczos: interpolation_type = 6; break; } cimg_image.resize(static_cast(width), static_cast(height), 1, static_cast(A.size()), interpolation_type); return cimg2channels(cimg_image); } image_channels_t imresize(const image_channels_t &A, double scale, image_interpolation m) { if (A.empty()) { return image_channels_t{}; } auto [h, w] = size(A[0]); return imresize(A, static_cast(h * scale), static_cast(w * scale), m); } void imwrite(const image_channels_t &A, const std::string &filename) { auto cimg_image = channels2cimg(A); cimg_image.save(filename.c_str()); } image_channel_t rgb2gray(const image_channels_t &A) { image_channel_t img(A[0].size(), image_row_t(A[0][0].size())); for (size_t i = 0; i < A.size(); ++i) { for (size_t j = 0; j < A[i].size(); ++j) { img[i][j] = (A[0][i][j] + A[1][i][j] + A[2][i][j]) / 3; } } return img; } image_channels_t gray2rgb(const image_channel_t &A, const std::vector> &colormap) { image_channels_t img( 3, image_channel_t(A.size(), image_row_t(A[0].size()))); for (size_t i = 0; i < A.size(); ++i) { for (size_t j = 0; j < A[i].size(); ++j) { color_array c = colormap_interpolation(A[i][j], 0, 255, colormap); img[0][i][j] = static_cast(round(c[1] * 255)); img[1][i][j] = static_cast(round(c[2] * 255)); img[2][i][j] = static_cast(round(c[3] * 255)); } } return img; } image_channels_t gray2rgb(const image_channel_t &A) { static std::vector> map = {{0, 0, 0}, {1, 1, 1}}; return gray2rgb(A, map); } image_channels_t gray2rgb(const image_channels_t &A, const std::vector> &colormap) { return gray2rgb(A[0], colormap); } image_channels_t gray2rgb(const image_channels_t &A) { return gray2rgb(A[0]); } void imwrite(const image_channel_t &A, const std::vector> &colormap, const std::string &filename) { imwrite(gray2rgb(A, colormap), filename); } image_channels_t imvignette(const image_channels_t &A, double min_radius, double exponent) { auto image = A; if (image.empty()) { return A; } if (image.size() < 2) { image = gray2rgb(image); } if (image.size() < 3) { image.emplace_back(image[0]); } auto [h, w] = size(image[0]); if (image.size() < 4) { image.emplace_back(image_channel_t(h, image_row_t(w, 255))); } double center_x = static_cast(w) / 2; double center_y = static_cast(h) / 2; double radius_sq = pow(min(h / 2, w / 2) * min_radius, 2); double max_t1 = pow(static_cast(h) - center_y, 2); double max_t2 = pow(static_cast(w) - center_x, 2); double max_t_minus_radius = (max_t1 + max_t2) - radius_sq; auto &alpha_channel = image[3]; for (size_t i = 0; i < h; ++i) { for (size_t j = 0; j < w; ++j) { double t1 = pow(static_cast(i) - center_y, 2); double t2 = pow(static_cast(j) - center_x, 2); if (t1 + t2 > radius_sq) { double norm_dist_from_r = ((t1 + t2) - radius_sq) / max_t_minus_radius; norm_dist_from_r = pow(norm_dist_from_r, exponent); alpha_channel[i][j] = static_cast< std::decay_t>( 255. * (1. - norm_dist_from_r)); } } } return image; } vector_2d transpose(const vector_2d &z) { vector_2d z2(z[0].size(), vector_1d(z.size())); for (size_t i = 0; i < z.size(); ++i) { for (size_t j = 0; j < z[0].size(); ++j) { z2[j][i] = z[i][j]; } } return z2; } std::vector tokenize(std::string_view text, std::string_view delimiters) { std::vector tokens; size_t pos = 0; while ((pos = text.find_first_not_of(delimiters, pos)) != std::string::npos) { size_t end_pos = text.find_first_of(delimiters, pos); tokens.emplace_back(text.substr(pos, end_pos - pos)); pos = end_pos; } return tokens; } std::pair, std::vector> wordcount(const std::vector &tokens, const std::vector &black_list, size_t max_cloud_size) { // count the frequency of each token const bool bl_sorted = std::is_sorted(black_list.begin(), black_list.end()); std::map word_frequency; for (const auto &token : tokens) { const bool is_in_black_list = bl_sorted ? std::binary_search(black_list.begin(), black_list.end(), token) : std::find(black_list.begin(), black_list.end(), token) != black_list.end(); if (!is_in_black_list) { auto it = word_frequency.find(token); if (it != word_frequency.end()) { ++word_frequency[token]; } else { word_frequency[token] = 1; } } } // sort tokens by frequency std::multimap> frequency_word; for (const auto &[token, count] : word_frequency) { frequency_word.emplace(count, token); } // get the max_cloud_size most frequent tokens std::vector unique_tokens; std::vector token_count; size_t i = 0; for (const auto &[count, token] : frequency_word) { unique_tokens.emplace_back(token); token_count.emplace_back(count); ++i; if (i > max_cloud_size) { break; } } // return tokens and counts return std::make_pair(unique_tokens, token_count); } std::pair, std::vector> wordcount(std::string_view text, const std::vector &black_list, std::string_view delimiters, size_t max_cloud_size) { auto tokens = tokenize(text, delimiters); return wordcount(tokens, black_list, max_cloud_size); } ticks_results calcticks(double limits_min, double limits_max, bool horizontal, double text_size, bool separateExp, bool log) { // Default limit on label length (in characters) constexpr size_t defMaxChars = 9; // Minimum value of the (upper) limit on label length (characters). // Setting this value too small will cause problems. Note: This value // does not affect the minimum length of the ticklabels. constexpr size_t minChars = 6; // Default font size for exponents (assumes that font units are // 'points') constexpr size_t defExpFontSize = 7; // Upper limit on number of ticks returned by CALCTICKS constexpr size_t initMaxTicks = 11; // Multiplier for textSize for vertical orientation when scale is 'log', // to account for labels using exponential notation. // constexpr double vertExpScale = 1.3; // Maximum number of characters in label string // Determines numerical precision displayed by the labels, // and also affects the tick spacing for horizontal orientation. size_t maxChars = defMaxChars; if (maxChars < minChars) { maxChars = minChars; } // Initial calculations if (log) { std::invalid_argument("Not implemented yet. The library does not " "need that as it is."); } // Data range double range = limits_max - limits_min; // Get eps values for rounding double lEps = eps(limits_min); double uEps = eps(limits_max); double minEps = min(lEps, uEps); // Vector of allowed tick counts std::vector testTickCounts = iota(2, initMaxTicks); // Make a list of rough intervals as a starting point std::vector roughInts = transform( testTickCounts, [&](double x) { return range / (x - 1); }); // Vector of 'nice' intervals std::vector niceVec = {1, 2, 5, 10}; // Find nice intervals // Normalize rough intervals by their scale std::vector decRoughInts = transform(roughInts, [](double x) { return floor(log10(x)); }); std::vector normRoughInts = transform(roughInts, decRoughInts, [](double x, double y) { return x / pow(10., y); }); // Get the distances to nice intervals, pick the shortest std::vector> deltas( niceVec.size(), std::vector(roughInts.size())); for (size_t i = 0; i < deltas.size(); ++i) { for (size_t j = 0; j < deltas[i].size(); ++j) { deltas[i][j] = std::abs(normRoughInts[j] - niceVec[i]); } } // idx of ticks with min distance std::vector idx(roughInts.size(), 0); for (size_t i = 0; i < deltas.size(); ++i) { for (size_t j = 0; j < deltas[i].size(); ++j) { if (deltas[i][j] < deltas[idx[j]][j]) { idx[j] = i; } } } // Get the nice intervals and scores std::vector niceInts(roughInts.size()); for (size_t i = 0; i < niceInts.size(); ++i) { niceInts[i] = niceVec[idx[i]] * pow(10, decRoughInts[i]); } // Remove duplicates niceInts = unique(niceInts); // Get upper and lower limits, fixed by the list of nice intervals. // Round out to make sure we get ticks at the original limits. std::vector lLims = transform( niceInts, [&](double x) { return floor(limits_min / x) * x; }); std::vector uLims = transform( niceInts, [&](double x) { return ceil(limits_max / x) * x; }); // Get tick counts using the list of nice intervals and limits std::vector nTicks(niceInts.size()); for (size_t i = 0; i < nTicks.size(); ++i) { nTicks[i] = floor(1 + (uLims[i] - lLims[i] + 10 * minEps) / niceInts[i]); } // Shrink nice limits that are outside of original limits for (size_t idx2 = 0; idx2 < lLims.size(); ++idx2) { if (lLims[idx2] < limits_min - 10 * eps(limits_min)) { nTicks[idx2] = nTicks[idx2] - 1; lLims[idx2] = lLims[idx2] + niceInts[idx2]; } if (uLims[idx2] > limits_max + 10 * eps(limits_min)) { nTicks[idx2] = nTicks[idx2] - 1; uLims[idx2] = uLims[idx2] - niceInts[idx2]; } } // Set values that are almost exactly the original limits to be the // original limit value. for (size_t idx2 = 0; idx2 < lLims.size(); ++idx2) { if (std::abs(lLims[idx2] - limits_min) < 10 * eps(limits_min)) { lLims[idx2] = limits_min; } if (std::abs(uLims[idx2] - limits_max) < 10 * eps(limits_max)) { uLims[idx2] = limits_max; } } // Discard values where the limits are reversed or equal size_t lim_idx = 0; while (lim_idx < lLims.size()) { if (lLims[lim_idx] >= uLims[lim_idx]) { lLims.erase(lLims.begin() + lim_idx); uLims.erase(uLims.begin() + lim_idx); nTicks.erase(nTicks.begin() + lim_idx); niceInts.erase(niceInts.begin() + lim_idx); } else { ++lim_idx; } } // Determine label size for each interval // Get the decade span of the limits and the decade of the intervals std::vector maxAbs = transform(lLims, uLims, [](double x, double y) { return std::abs(x) > std::abs(y) ? std::abs(x) : std::abs(y); }); std::vector decMax = transform( maxAbs, [](double x) { return floor(log10(x)); }); // - nDec; std::vector decInts = transform( niceInts, [](double x) { return floor(log10(x)); }); // - nDec; // Get the number of characters needed for tick labels for normal // notation std::vector labelChars = transform(decInts, decMax, [&](double x, double y) { return max(y + 1, 1.) + ((x < 0) ? (1 - x) : 0.); }); for (size_t i = 0; i < labelChars.size(); ++i) { if (labelChars[i] > static_cast(maxChars - 1)) { labelChars[i] = static_cast(maxChars - 1); } } // Handle exponential notation // Determine whether or not to use exponential notation std::vector isExp(decInts.size()); if (separateExp) { for (size_t i = 0; i < decInts.size(); ++i) { // Large numbers: isExp[i] = (decMax[i] > 6) || (decMax[i] == 6 && decInts[i] > 0); // Small numbers: isExp[i] = isExp[i] || (decMax[i] < -3) || (decMax[i] == -3 && decInts[i] < -5); } } else { for (size_t i = 0; i < decInts.size(); ++i) { // Large numbers: isExp[i] = decMax[i] > 6 || (decMax[i] == 6 && decInts[i] > 3); // Small numbers: isExp[i] = isExp[i] || (decMax[i] < -3) || (decMax[i] == -3 && decInts[i] < -5); } } // Get length of exponential labels depending on style std::vector expChars; if (separateExp) { expChars = transform(decInts, decMax, [&](double x, double y) { return 2 + max(0., min(static_cast(maxChars) - 3, y - x)); }); } else { expChars = transform(decInts, decMax, [&](double x, double y) { return 2 + max(0., min(max(0., static_cast(maxChars) - 7), y - x)) + 4; }); } // Select between normal and exponential label lengths for (size_t i = 0; i < labelChars.size(); ++i) { if (isExp[i]) { labelChars[i] = expChars[i]; } } // For consistency, always include space for a negative sign, regardless // of the sign of the actual limits labelChars = transform(labelChars, [](double x) { return x + 1; }); // Uncomment to not include the negative sign space if it isn't needed // labelChars = labelChars + (lLims < 0); // Get label size based on textSize, orientation and length of label // string std::vector labelSize; if (horizontal) { labelSize = transform(labelChars, [&](double labelChars) { return text_size * labelChars; }); } else { labelSize = std::vector(labelChars.size(), text_size); // labelSize = transform(labelChars, [&](double labelChars) { return // text_size; }); } // Choose the best interval // Maximum number of ticks without overlapping labels std::vector nMax(uLims.size()); for (size_t i = 0; i < nMax.size(); ++i) { nMax[i] = floor((uLims[i] - lLims[i] + 10 * minEps) / labelSize[i]) + 1; } // Modify this value based on initMaxTicks std::vector nMaxScore = transform(nMax, [&](double x) { return min(x, initMaxTicks + 0.25 * (x - initMaxTicks)); }); // Calculate a score based on the number of ticks relative to the // maximum. std::vector nTickScore = transform(nTicks, nMaxScore, [](double x, double y) { return 1 - pow((x / y - 0.7), 2); }); // Severe penalty for more than nMax ticks std::vector penalty = transform( nTicks, nMax, [](double x, double y) { return 4 * x / y; }); for (size_t i = 0; i < nTicks.size(); ++i) { if (nTicks[i] / nMax[i] <= 1.) { penalty[i] = 0; } } // Penalty for more than 0.75*nMax but less than nMax std::vector penaltyScale = transform(nTicks, nMax, [](double x, double y) { return x / y; }); for (size_t i = 0; i < penaltyScale.size(); ++i) { if (penaltyScale[i] > 0.75 && nTicks[i] / nMax[i] <= 1.) { penalty[i] = 1 * nTickScore[i] * penaltyScale[i]; } } // Test for intervals that divide the limits exactly std::vector rangeTest = transform(niceInts, [&](double x) { return range / x; }); std::vector isInt(rangeTest.size()); for (size_t i = 0; i < rangeTest.size(); ++i) { isInt[i] = std::abs(rangeTest[i] - round(rangeTest[i])) < 1e-6; } // Test for intervals that land exactly on the endpoints std::vector hitsEnds(lLims.size()); for (size_t i = 0; i < hitsEnds.size(); ++i) { hitsEnds[i] = (std::abs(lLims[i] - limits_min) < 100 * lEps) && (std::abs(uLims[i] - limits_max) < 100 * uEps); } // Compute a score using the above tests and the tick score and penalty std::vector scores(isInt.size()); for (size_t i = 0; i < isInt.size(); ++i) { scores[i] = isInt[i] + 0.75 * hitsEnds[i] + nTickScore[i] - penalty[i]; } // Penalize for too few ticks for (size_t i = 0; i < scores.size(); ++i) { if ((nMax[i] > 5) && (nTicks[i] < 4)) { scores[i] = scores[i] - 0.5 * scores[i]; } } for (size_t i = 0; i < scores.size(); ++i) { if ((nMax[i] > 4) && (nTicks[i] == 2)) { scores[i] = scores[i] - 0.5 * scores[i]; } } // Find the highest score auto max_it = std::max_element(scores.begin(), scores.end()); double maxScore = *max_it; size_t bestIdx = max_it - scores.begin(); double lLim; double uLim; double interval; if (maxScore > -1) { // Use the limits and interval with the best score lLim = lLims[bestIdx]; uLim = uLims[bestIdx]; interval = niceInts[bestIdx]; } else { // If the best score is too low, just return two ticks at the limits lLim = limits_min; uLim = limits_max; interval = range; } // Create the vector of ticks, making sure to hit lLims and uLims // exactly std::vector ticks = iota(lLim, interval, (uLim - interval / 2)); ticks.emplace_back(uLim); // Handle 0 as a special case for (size_t i = 0; i < ticks.size(); ++i) { if (std::abs(ticks[i]) < 10 * minEps) { ticks[i] = 0; } } // Calculate how far the label overhangs (in data units) from both ends // (0 <= overhang <= labelSize/2) std::vector overhang = { max(0., limits_min + labelSize[bestIdx] / 2. - lLim), max(0., uLim - limits_max + labelSize[bestIdx] / 2)}; // Create tick labels // Get the decade span of the limits and the decade of the intervals double maxAbs_d = max(std::abs(ticks.front()), std::abs(ticks.back())); double decMax_d = floor(log10(maxAbs_d)); double decInt = floor(log10(interval)); ticks_results result; if (isExp[bestIdx] && separateExp) { // Exponential using 'nice' notation w/ separate exponent string: // Label: '1.23' scaleStr: 'x 10^34' // Determine maximum number of decimals and set formatting string int n = static_cast( max(1., min(max(0., static_cast(maxChars - 3)), decMax_d - decInt))); std::string fStr = "%." + num2str(n) + 'f'; // Normalize the ticks to the correct scale and create the labels std::vector normTicks = transform( ticks, [&](double x) { return x / pow(10, decMax_d); }); std::vector tickLabels(normTicks.size()); for (size_t i = 0; i < tickLabels.size(); ++i) { tickLabels[i] = num2str(normTicks[i], fStr); rtrim(tickLabels[i]); ltrim(tickLabels[i]); } // Create the scale string // Use smaller font for exponent if requested std::string fs; // Use smaller font for exponents if requested constexpr bool smallExp = true; if (smallExp) { constexpr double expFontSize = defExpFontSize; fs = "\\fontsize{" + num2str(expFontSize) + "}"; } std::string scaleStr = "x 10^{" + fs + num2str(decMax_d) + "}"; result.ticks = ticks; result.tickLabels = tickLabels; result.scaleStr = scaleStr; result.overhang = overhang; } else if (isExp[bestIdx]) { // Exponential using 'ugly' notation: '-2.34e+301' // Determine maximum number of decimals and set formatting string int n = static_cast( max(0., min(max(0., static_cast(maxChars) - 7), decMax_d - decInt))); std::string fStr = "%." + num2str(n) + "e"; // Create tick labels std::vector tickLabels(ticks.size()); for (size_t i = 0; i < tickLabels.size(); ++i) { tickLabels[i] = num2str(ticks[i], fStr); rtrim(tickLabels[i]); ltrim(tickLabels[i]); } // Output 'e' for the scale string as a flag that we are using // exponential notation. std::string scaleStr = "e"; result.ticks = ticks; result.tickLabels = tickLabels; result.scaleStr = scaleStr; result.overhang = overhang; } else { // Normal (fixed point) notation // Determine maximum number of decimals and set formatting string int n = static_cast(max( 0., min(-decInt, max(0., static_cast(maxChars) - 3) - decMax_d))); std::string fStr = "%." + num2str(n) + "f"; // Create tick labels std::vector tickLabels(ticks.size()); for (size_t i = 0; i < tickLabels.size(); ++i) { tickLabels[i] = num2str(ticks[i], fStr); rtrim(tickLabels[i]); ltrim(tickLabels[i]); } std::string scaleStr = ""; for (size_t i = 0; i < tickLabels.size(); ++i) { if (std::abs(ticks[i]) < 10 * minEps) { tickLabels[i] = "0"; } } result.ticks = ticks; result.tickLabels = tickLabels; result.scaleStr = scaleStr; result.overhang = overhang; } return result; } double distance(double x1, double y1, double x2, double y2) { return sqrt(pow(x2 - x1, 2) + pow(y2 - y1, 2)); } } // namespace matplot