VisualStudio 2022 C++20 errors
- std::min, std::max ambiguous: cast was needed e.g. here: const auto padded_end = std::min((const int)max_size, (const int)(raw_end + PADDING_POINTS));
- Class Observable is missing the == Operator: add bool operator==(const T& Val) { return Val == value; }
Thanks, can you fix it in pr?
Sure - there is also another issue using C++17 were in a != comparison the order must be different. The compiler misses then the != operator (not defined in enumeration object) I seem not to have the credentials to push changes. I generated a new branch (Martin-MM), checked that out but Github does not allow me to push (and the generate a pull request) although my credentials for GitHub are correct. I can mail the 3 Files. Just send an eMail to [email protected] and I will send back the Sources
Hi Frans,
I have attached the 3 files. Will try to resolve my GitHub problem later
BR
Martin
From: Frans Rosencrantz @.> Sent: Saturday, March 8, 2025 5:15 PM To: franshej/CustomMatPlot @.> Cc: Martin Moerz @.>; Author @.> Subject: Re: [franshej/CustomMatPlot] VisualStudio 2022 C++20 errors (Issue #55)
Thanks, can you fix it in pr?
— Reply to this email directly, view it on GitHub https://github.com/franshej/CustomMatPlot/issues/55#issuecomment-2708374536 , or unsubscribe https://github.com/notifications/unsubscribe-auth/ABLXBT5Q3JIGGPLKLNKTBKL2TMJPLAVCNFSM6AAAAABYTAIGHKVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDOMBYGM3TINJTGY . You are receiving this because you authored the thread.Message ID: @.***>
franshej left a comment (franshej/CustomMatPlot#55) https://github.com/franshej/CustomMatPlot/issues/55#issuecomment-2708374536
Thanks, can you fix it in pr?
— Reply to this email directly, view it on GitHub https://github.com/franshej/CustomMatPlot/issues/55#issuecomment-2708374536 , or unsubscribe https://github.com/notifications/unsubscribe-auth/ABLXBT5Q3JIGGPLKLNKTBKL2TMJPLAVCNFSM6AAAAABYTAIGHKVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDOMBYGM3TINJTGY . You are receiving this because you authored the thread.Message ID: @.***>
/**
- Copyright (c) 2022 Frans Rosencrantz
- This software is released under the MIT License.
- https://opensource.org/licenses/MIT */
#include "cmp_plot.h"
#include
#include "cmp_datamodels.h" #include "cmp_frame.h" #include "cmp_graph_area.h" #include "cmp_graph_line.h" #include "cmp_grid.h" #include "cmp_label.h" #include "cmp_legend.h" #include "cmp_lookandfeel.h" #include "cmp_trace.h" #include "cmp_utils.h" #include "juce_core/system/juce_PlatformDefs.h"
namespace cmp {
static std::pair<float, float> findMinMaxValuesInGraphLines(
const std::vector<std::unique_ptrcmp::GraphLine>& graph_lines,
const bool isXValue) noexcept {
auto max_value = -std::numeric_limits
for (const auto& graph : graph_lines) { const auto& values = isXValue ? graph->getXData() : graph->getYData();
if (!values.empty()) {
const auto& current_max = *std::max_element(values.begin(), values.end());
max_value = current_max > max_value ? current_max : max_value;
const auto& current_min = *std::min_element(values.begin(), values.end());
min_value = current_min < min_value ? current_min : min_value;
}
}
return {min_value, max_value}; }
template <class ValueType> static Lim<ValueType> createLimsIfTheSame(const Lim<ValueType>& lims) noexcept { if (lims.min == lims.max) { const auto one = ValueType(1); return {lims.min - one, lims.max + one}; } return lims; }
template
juce::Point
const auto graph_line_exsists = std::find_if(m_graph_lines->begin(), m_graph_lines->end(), [&graphline](const auto& gl) { return gl.get() == graphline; }) != m_graph_lines->end();
const GraphLine* nearest_graph_line{graph_line_exsists ? graphline : nullptr};
if (!nearest_graph_line) { for (const auto& graph_line : *m_graph_lines) { auto current_data_point_index = size_t{0}; if constexpr (is_point_data_point) { const auto [data_point, data_point_index_temp] = graph_line->findClosestDataPointTo(point, false, false);
current_data_point = current_point = data_point;
data_point_index = data_point_index_temp;
} else {
const auto [current_pixel_point, data_point, data_point_index_temp] =
graph_line->findClosestPixelPointTo(point);
current_point = current_pixel_point;
current_data_point = data_point;
current_data_point_index = data_point_index_temp;
}
if (point.getDistanceFrom(current_point) <
point.getDistanceFrom(closest_point)) {
closest_point = current_point;
closest_data_point = current_data_point;
nearest_graph_line = graph_line.get();
data_point_index = current_data_point_index;
}
}
} else if (graph_line_exsists) { if constexpr (is_point_data_point) { const auto [data_point, data_point_index_temp] = nearest_graph_line->findClosestDataPointTo(point); return {data_point_index_temp, nearest_graph_line}; }
const auto [pixel_point, data_point, data_point_index_temp] =
nearest_graph_line->findClosestPixelPointTo(point);
closest_point = pixel_point;
closest_data_point = data_point;
data_point_index = data_point_index_temp;
}
return {data_point_index, nearest_graph_line}; }
void Plot::resetLookAndFeelChildrens(juce::LookAndFeel* lookandfeel) { for (auto* child : getChildren()) { child->setLookAndFeel(lookandfeel); }
m_trace->setLookAndFeel(lookandfeel); }
Plot::~Plot() { setLookAndFeel(nullptr); }
Plot::Plot(const Scaling x_scaling, const Scaling y_scaling) : m_x_scaling(ObserverId::XScaling, x_scaling), m_y_scaling(ObserverId::YScaling, y_scaling), m_x_lim(ObserverId::XLim), m_y_lim(ObserverId::YLim), m_graph_bounds(ObserverId::GraphBounds), m_downsampling_type(ObserverId::DownsamplingType, DownsamplingType::xy_downsampling), m_notify_components_on_update(ObserverId::Undefined), m_graph_lines(std::make_unique<GraphLineList>()), m_plot_label(std::make_unique<PlotLabel>()), m_frame(std::make_unique<Frame>()), m_legend(std::make_unique<Legend>()), m_selected_area(std::make_unique<GraphArea>()), m_grid(std::make_unique<Grid>()), m_trace(std::make_unique<Trace>()) { m_graph_bounds.addObserver(*m_grid, *m_trace); m_x_scaling.addObserver(*m_grid, *m_selected_area, *m_trace); m_y_scaling.addObserver(*m_grid, *m_selected_area, *m_trace); m_x_lim.addObserver(*m_grid, *m_selected_area, *m_trace); m_y_lim.addObserver(*m_grid, *m_selected_area, *m_trace); m_notify_components_on_update.addObserver(*m_grid);
setLookAndFeel(getDefaultLookAndFeel());
addAndMakeVisible(m_grid.get()); addChildComponent(m_legend.get()); addAndMakeVisible(m_selected_area.get()); addAndMakeVisible(m_plot_label.get()); addAndMakeVisible(m_frame.get());
m_legend->setAlwaysOnTop(true); m_selected_area->toBehind(m_legend.get()); m_grid->toBack();
m_grid->onGridLabelLengthChanged = [this](cmp::Grid* grid) { this->resizeChildrens(); };
m_legend->onNumberOfDescriptionsChanged = [this](const auto& desc) { if (auto lnf = getPlotLookAndFeel()) { const auto legend_bounds = lnf->getLegendBounds(m_graph_bounds, desc);
m_legend->setBounds(legend_bounds);
}
};
m_trace->onTracePointChanged = [this](const auto* trace_point, const auto prev_data, const auto new_data) { if (onTraceValueChange) { onTraceValueChange(trace_point, prev_data, new_data); } };
this->setWantsKeyboardFocus(true); }
PlotLookAndFeel* Plot::getPlotLookAndFeel() { auto* lnf = &getLookAndFeel(); return dynamic_cast<PlotLookAndFeel*>(lnf); }
void Plot::updateXLim(const Lim_f& new_x_lim) { if (new_x_lim.min > new_x_lim.max) UNLIKELY throw std::invalid_argument("Min value must be lower than max value.");
UNLIKELY if ((std::abs(new_x_lim.max - new_x_lim.min) <
std::numeric_limits
UNLIKELY if (m_x_scaling == Scaling::logarithmic && new_x_lim.isMinOrMaxZero()) { throw std::invalid_argument( "The min/max x-value is zero or 'xLim' has been called with a zero " "value. 10log(0) = -inf"); }
if (new_x_lim && new_x_lim != m_x_lim) { m_x_lim = new_x_lim; } }
void Plot::updateYLim(const Lim_f& new_y_lim) { if (new_y_lim.min > new_y_lim.max) UNLIKELY throw std::invalid_argument("Min value must be lower than max value.");
UNLIKELY if (std::abs(new_y_lim.max - new_y_lim.min) <
std::numeric_limits
UNLIKELY if (m_y_scaling == Scaling::logarithmic && new_y_lim.isMinOrMaxZero()) { throw std::invalid_argument( "The min/max y-value is zero or 'yLim' has been called with a zero " "value. 10log(0) = -inf"); }
if (new_y_lim && m_y_lim.getValue() != new_y_lim) { m_y_lim = new_y_lim; } }
template <class ValueType> static auto getLimOffset(const ValueType min, const ValueType max, const cmp::Scaling scaling) { auto new_min = min;
if (scaling == Scaling::linear) { const auto diff = (max - min) / decltype(min)(20);
const auto new_min_offset = min - diff;
const auto new_max_offset = max + diff;
return Lim<decltype(new_min)>(new_min_offset, new_max_offset);
}
return Lim<decltype(new_min)>(min, max); }
void Plot::setAutoXScale() { const auto [min, max] = findMinMaxValuesInGraphLines(*m_graph_lines, true);
m_x_lim_start = getLimOffset<decltype(min)>(min, max, m_x_scaling); m_x_lim_start = createLimsIfTheSame(m_x_lim_start);
updateXLim(m_x_lim_start); }
void Plot::setAutoYScale() { const auto [min, max] = findMinMaxValuesInGraphLines(*m_graph_lines, false);
m_y_lim_start = getLimOffset<decltype(min)>(min, max, m_y_scaling); m_y_lim_start = createLimsIfTheSame(m_y_lim_start);
updateYLim(m_y_lim_start); }
void Plot::xLim(const float min, const float max) { updateXLim({min, max}); m_x_lim_start = {min, max}; m_x_autoscale = false; }
void Plot::yLim(const float min, const float max) { updateYLim({min, max}); m_y_lim_start = {min, max}; m_y_autoscale = false; }
template <typename ValueType> static std::pair<std::vector<std::vector<ValueType>>, std::vector<std::vector<ValueType>>> prepareDataForVerticalOrHorizontalLines( const std::vector<ValueType>& coordinates, Lim<ValueType> limits) { if (coordinates.empty()) return {{}, {}};
std::vector<std::vector<ValueType>> lines_start_end(coordinates.size(), {limits.min, limits.max}); std::vector<std::vector<ValueType>> line_coordinates(coordinates.size());
auto line_coordinate_it = line_coordinates.begin(); for (auto& x : coordinates) { *line_coordinate_it++ = {x, x}; }
return {lines_start_end, line_coordinates}; }
void Plot::plotHorizontalLines(const std::vector
plotInternalGraphLineType::horizontal(y_data, x_data, graph_attributes); }
void Plot::plotVerticalLines(const std::vector
plotInternalGraphLineType::vertical(y_data, x_data, graph_attributes); }
template <GraphLineType t_graph_line_type>
void Plot::plotInternal(const std::vector<std::vector
updateGraphLineYData<t_graph_line_type>(y_data, graph_attributes);
if (update_y_data_only) { goto skip_update_x_data_label; }
if (!x_data.empty()) { updateGraphLineXData<t_graph_line_type>(x_data); } else { // Fix this for other types of lines const auto gen_x_data = generateXdataRamp(y_data);
if (!gen_x_data.empty()) {
updateGraphLineXData<t_graph_line_type>(gen_x_data);
}
}
skip_update_x_data_label: m_notify_components_on_update.notify(); }
std::vector<std::vector
return generateRamp(); }
void Plot::plot(const std::vector<std::vector
void Plot::plotUpdateYOnly(const std::vector<std::vector
void Plot::fillBetween( const std::vector<GraphSpreadIndex>& graph_spread_indices, const std::vectorjuce::Colour& fill_area_colours) { m_graph_spread_list.resize(graph_spread_indices.size()); auto graph_spread_it = m_graph_spread_list.begin();
for (const auto& spread_index : graph_spread_indices) { if (std::max(spread_index.first_graph, spread_index.second_graph) >= m_graph_lines->sizeGraphLineType::any()) { throw std::range_error("Spread index out of range."); } const auto first_graph = (*m_graph_lines)[spread_index.first_graph].get(); const auto second_graph = (*m_graph_lines)[spread_index.second_graph].get();
auto it_colour = fill_area_colours.begin();
const auto colour = it_colour != fill_area_colours.end()
? *it_colour++
: first_graph->getColour();
auto& graph_spread = *graph_spread_it++;
if (!graph_spread) {
graph_spread =
std::make_unique<GraphSpread>(first_graph, second_graph, colour);
graph_spread->setBounds(m_graph_bounds);
graph_spread->setLookAndFeel(getPlotLookAndFeel());
addAndMakeVisible(graph_spread.get());
graph_spread->toBehind(m_selected_area.get());
} else {
graph_spread->m_lower_bound = first_graph;
graph_spread->m_upper_bound = second_graph;
graph_spread->m_spread_colour = colour;
graph_spread->setBounds(m_graph_bounds);
}
} }
void cmp::Plot::setDownsamplingType(const DownsamplingType downsampling_type) { this->setDownsamplingTypeInternal(downsampling_type); }
void cmp::Plot::setDownsamplingTypeInternal( const DownsamplingType downsampling_type) { if (downsampling_type > DownsamplingType::no_downsampling && m_pixel_point_move_type > PixelPointMoveType::none) { jassertfalse; // You can't change the downsampling type if you configured // it possible to move the pixel points. Call // 'setPixelPointMoveType()' with a value of // 'PixelPointMoveType::none' first. m_pixel_point_move_type = PixelPointMoveType::none; } else { m_downsampling_type = downsampling_type; } }
template <class ValueType> static auto jassetLogLimBelowZero(const cmp::Scaling scaling, const Lim<ValueType> lim) { const auto zero = decltype(lim.min)(0);
// You are trying to use a negative limit but the axis is logarithmic. Don't // do that. Use ylim or xlim to set limits above 0. Then call 'setScaling()' // if that is used. jassert(!(scaling == Scaling::logarithmic && (lim.min <= zero || lim.max <= zero))); }
void Plot::setScaling(const Scaling x_scaling, const Scaling y_scaling) noexcept { if (m_x_scaling != x_scaling || m_y_scaling != y_scaling) { jassetLogLimBelowZero(x_scaling, m_x_lim_start); jassetLogLimBelowZero(y_scaling, m_y_lim_start);
m_x_scaling = x_scaling;
m_y_scaling = y_scaling;
} }
void Plot::setXLabel(const std::string& x_label) { m_plot_label->setXLabel(x_label);
resizeChildrens(); }
void Plot::setXTickLabels(const std::vectorstd::string& x_labels) { m_grid->setXLabels(x_labels); }
void Plot::setYTickLabels(const std::vectorstd::string& y_labels) { m_grid->setYLabels(y_labels); }
void Plot::setXTicks(const std::vector
void Plot::setYTicks(const std::vector
void Plot::setYLabel(const std::string& y_label) { m_plot_label->setYLabel(y_label);
resizeChildrens(); }
void Plot::setTitle(const std::string& title) { m_plot_label->setTitle(title);
resizeChildrens(); }
void Plot::setTracePoint(const juce::Point
// Set trace point internal
void Plot::setTracePointInternal(
const juce::Point
m_trace->addOrRemoveTracePoint(nearest_graph_line, data_point_index); m_trace->addAndMakeVisibleTo(this); }
void Plot::setGridType(const GridType grid_type) { m_grid->setGridType(grid_type); }
void Plot::clearTracePoints() noexcept { m_trace->clear(); }
void Plot::resizeChildrens() { if (auto lnf = getPlotLookAndFeel()) { const auto plot_bound = lnf->getPlotBounds(getBounds()); const auto graph_bound = lnf->getGraphBounds(getBounds(), this);
if (!graph_bound.isEmpty()) {
m_graph_bounds = graph_bound;
constexpr auto margin_for_1px_outside = 1;
const juce::Rectangle<int> frame_bound = {
graph_bound.getX(), graph_bound.getY(),
graph_bound.getWidth() + margin_for_1px_outside,
graph_bound.getHeight() + margin_for_1px_outside};
m_grid->setBounds(plot_bound);
m_plot_label->setBounds(plot_bound);
m_frame->setBounds(frame_bound);
m_selected_area->setBounds(graph_bound);
for (const auto& graph_line : *m_graph_lines) {
if (graph_line) graph_line->setBounds(graph_bound);
}
for (const auto& spread : m_graph_spread_list) {
spread->setBounds(graph_bound);
}
if (m_legend) {
auto legend_bounds = m_legend->getBounds();
const auto legend_postion =
lnf->getLegendPosition(graph_bound, legend_bounds);
legend_bounds.setPosition(legend_postion);
m_legend->setBounds(legend_bounds);
}
}
} }
void Plot::resized() { resizeChildrens(); }
void Plot::paint(juce::Graphics& g) { if (getPlotLookAndFeel()) { auto lnf = getPlotLookAndFeel();
lnf->drawBackground(g, m_graph_bounds);
} }
void Plot::parentHierarchyChanged() { auto* parentComponent = getParentComponent(); if (parentComponent) { parentComponent->addMouseListener(this, true); } lookAndFeelChanged(); }
PlotLookAndFeel* Plot::getDefaultLookAndFeel() { if (!m_lookandfeel_default) m_lookandfeel_default = std::make_unique<PlotLookAndFeel>(); return m_lookandfeel_default.get(); }
void Plot::lookAndFeelChanged() { if (auto* lnf = dynamic_cast<LookAndFeelMethods*>(&getLookAndFeel())) { resetLookAndFeelChildrens(lnf); resizeChildrens(); } else { resetLookAndFeelChildrens(nullptr); } }
template <GraphLineType t_graph_line_type> void Plot::addGraphLineInternal(std::unique_ptr<GraphLine>& graph_line, const size_t graph_line_index) { auto lnf = getPlotLookAndFeel(); graph_line = std::make_unique<GraphLine>();
m_graph_bounds.addObserver(*graph_line); m_downsampling_type.addObserver(*graph_line); m_x_scaling.addObserver(*graph_line); m_y_scaling.addObserver(*graph_line); m_x_lim.addObserver(*graph_line); m_y_lim.addObserver(*graph_line); m_notify_components_on_update.addObserver(*graph_line);
const auto colour_id = lnf->getColourFromGraphID(graph_line_index); const auto graph_colour = lnf->findAndGetColourFromId(colour_id);
graph_line->setColour(graph_colour); graph_line->setLookAndFeel(lnf); graph_line->setBounds(m_graph_bounds); graph_line->setType(t_graph_line_type);
addAndMakeVisible(graph_line.get()); graph_line->toBehind(m_selected_area.get()); }
template <GraphLineType t_graph_line_type>
void Plot::updateGraphLineYData(
const std::vector<std::vector
UNLIKELY if (y_data.size() != m_graph_lines->size<t_graph_line_type>()) { m_graph_lines->resize<t_graph_line_type>(y_data.size()); std::size_t graph_line_index = 0u; for (auto& graph_line : *m_graph_lines) { if (graph_line == nullptr) { addGraphLineInternal<t_graph_line_type>(graph_line, graph_line_index); } graph_line_index++; } m_legend->setGraphLines(*m_graph_lines); }
auto y_data_it = y_data.begin(); for (const auto& graph_line : *m_graph_lines) { if (graph_line->getType() == t_graph_line_type) { if (y_data_it == y_data.end()) throw std::range_error( "Y_data out of range, internal error, please create an issue " "on " "Github with a test case that triggers this error."); graph_line->setYValues(*y_data_it++); } }
UNLIKELY if (m_y_autoscale && !m_is_panning_or_zoomed_active) { setAutoYScale(); }
if (!graph_attribute_list.empty()) { auto it_gal = graph_attribute_list.begin(); for (const auto& graph_line : *m_graph_lines) { if (it_gal != graph_attribute_list.end() && graph_line->getType() == t_graph_line_type) { graph_line->setGraphAttribute(*it_gal++); } } } }
template void Plot::updateGraphLineYDataGraphLineType::normal(
const std::vector<std::vector
template <GraphLineType t_graph_line_type>
void Plot::updateGraphLineXData(const std::vector<std::vector
auto x_data_it = x_data.begin(); for (const auto& graph : *m_graph_lines) { if (graph->getType() == t_graph_line_type) { graph->setXValues(*x_data_it++); } }
if (m_x_autoscale && !m_is_panning_or_zoomed_active) { setAutoXScale(); } }
void Plot::setLegend(const StringVector& graph_descriptions) { m_legend->setVisible(true); m_legend->setLegend(graph_descriptions); }
void Plot::addOrRemoveTracePoint(const juce::MouseEvent& event) { const auto component_pos = event.eventComponent->getBounds().getPosition();
const auto mouse_pos = getMousePositionRelativeToGraphArea(event);
const auto [data_point_index, nearest_graph_line] =
findNearestPoint
m_trace->addOrRemoveTracePoint(nearest_graph_line, data_point_index, TracePointVisibilityType::visible); m_trace->addAndMakeVisibleTo(this); }
void Plot::mouseHandler(const juce::MouseEvent& event, const UserInputAction user_input) { switch (user_input) { case UserInputAction::create_tracepoint: { addOrRemoveTracePoint(event); break; } case UserInputAction::move_tracepoint_to_closest_point: { moveTracepoint(event); break; } case UserInputAction::move_tracepoint_label: { moveTracepointLabel(event); break; } case UserInputAction::move_legend: { moveLegend(event); break; } case UserInputAction::select_tracepoint: { selectTracePoint(event); break; } case UserInputAction::deselect_tracepoint: { deselectTracePoint(event); break; } case UserInputAction::select_tracepoints_within_selected_area: { selectedTracePointsWithinSelectedArea(); break; } case UserInputAction::select_area_start: { setStartPosSelectedRegion(event.getPosition()); break; } case UserInputAction::select_area_draw: { drawSelectedRegion(event.getPosition()); break; } case UserInputAction::zoom_selected_area: { zoomOnSelectedRegion(); break; } case UserInputAction::zoom_in: { break; } case UserInputAction::zoom_out: { break; } case UserInputAction::zoom_reset: { resetZoom(); break; } case UserInputAction::create_movable_pixel_point: { break; } case UserInputAction::move_selected_trace_points: { moveSelectedTracePoints(event); break; } case UserInputAction::panning: { panning(event); break; } case UserInputAction::remove_movable_pixel_point: { break; } default: { break; } } }
void Plot::selectTracePoint(const juce::MouseEvent& event) { m_trace->selectTracePoint(event.eventComponent, true); }
void Plot::deselectTracePoint(const juce::MouseEvent& event) { for (auto& trace_point : m_trace->getTraceLabelPoints()) { m_trace->selectTracePoint(trace_point.trace_point.get(), false); } }
void Plot::moveSelectedTracePoints(const juce::MouseEvent& event) { const auto mouse_pos = getMousePositionRelativeToGraphArea(event);
auto d_data_position = getDataPointFromPixelCoordinate( mouse_pos, m_graph_bounds.getValue().toFloat(), m_x_lim, m_x_scaling, m_y_lim, m_y_scaling) - getDataPointFromPixelCoordinate( m_prev_mouse_position, m_graph_bounds.getValue().toFloat(), m_x_lim, m_x_scaling, m_y_lim, m_y_scaling);
switch (m_pixel_point_move_type) { case PixelPointMoveType::horizontal: { d_data_position.setY(0.f); break; } case PixelPointMoveType::vertical: { d_data_position.setX(0.f); break; } case PixelPointMoveType::horizontal_vertical: { break; } default: { break; } }
std::map<GraphLine*, std::vector<size_t>> graph_line_data_point_map; for (const auto& trace_label_point : m_trace->getTraceLabelPoints()) { if (!trace_label_point.isSelected()) continue; const auto graph_line = trace_label_point.trace_point->associated_graph_line; const auto data_point_index = trace_label_point.trace_point->data_point_index;
for (const auto& graph : *m_graph_lines) {
if (graph.get() == graph_line) {
graph->movePixelPoint(d_data_position, data_point_index);
graph_line_data_point_map[graph.get()].push_back(data_point_index);
break;
}
}
}
for (auto& [graph_line, indices_to_update] : graph_line_data_point_map) { graph_line->setIndicesToUpdate(indices_to_update); }
m_trace->updateAllTracePoints(); m_prev_mouse_position = mouse_pos;
repaint();
if (m_graph_lines_changed_callback) { m_graph_lines_changed_callback(createGraphLineDataViewList(*m_graph_lines)); } }
void Plot::resetZoom() { m_is_panning_or_zoomed_active = false; updateXLim(m_x_lim_start); updateYLim(m_y_lim_start); repaint(); }
void Plot::setStartPosSelectedRegion(const juce::Point
m_selected_area->setStartPosition(start_position); }
void Plot::drawSelectedRegion(const juce::Point
void Plot::zoomOnSelectedRegion() {
m_is_panning_or_zoomed_active = true;
const auto data_bound = m_selected_area->getDataBound
updateXLim({data_bound.getX(), data_bound.getX() + data_bound.getWidth()}); updateYLim({data_bound.getY(), data_bound.getY() + data_bound.getHeight()});
m_selected_area->reset(); repaint(); }
void Plot::selectedTracePointsWithinSelectedArea() {
const auto selected_area = m_selected_area->getSelectedAreaBound
for (auto& trace_point : m_trace->getTraceLabelPoints()) { auto trace_point_pos = trace_point.trace_point->getBounds().getPosition() - m_graph_bounds->getPosition() + margin;
if (selected_area.contains(trace_point_pos)) {
m_trace->selectTracePoint(trace_point.trace_point.get(), true);
}
}
m_trace->addAndMakeVisibleTo(this); m_selected_area->reset(); repaint(); }
void Plot::moveTracepoint(const juce::MouseEvent& event) { auto bounds = event.eventComponent->getBounds();
const auto mouse_pos = bounds.getPosition() - m_graph_bounds->getPosition() + event.getEventRelativeTo(event.eventComponent).getPosition();
const auto* associated_graph_line = m_trace->getAssociatedGraphLine(event.eventComponent);
const auto [data_point_index, nearest_graph_line] = findNearestPoint(mouse_pos.toFloat(), associated_graph_line);
m_trace->setDataPointFor(event.eventComponent, data_point_index, nearest_graph_line); }
void Plot::moveTracepointLabel(const juce::MouseEvent& event) { auto bounds = event.eventComponent->getBounds();
const auto mouse_pos = bounds.getPosition() + event.getEventRelativeTo(event.eventComponent).getPosition();
if (m_trace->setCornerPositionForLabelAssociatedWith(event.eventComponent, mouse_pos)) { m_trace->updateSingleTracePoint(event.eventComponent); } }
void Plot::moveLegend(const juce::MouseEvent& event) { m_comp_dragger.dragComponent(event.eventComponent, event, nullptr); }
void Plot::mouseDown(const juce::MouseEvent& event) { if (isVisible()) { m_prev_mouse_position = getMousePositionRelativeToGraphArea(event);
const auto lnf = getPlotLookAndFeel();
if (m_selected_area.get() == event.eventComponent) {
if (event.mods.isRightButtonDown()) {
mouseHandler(
event, lnf->getUserInputAction(UserInput::right | UserInput::drag |
UserInput::graph_area));
}
}
if (m_trace->isComponentTracePoint(event.eventComponent)) {
if (!event.mods.isRightButtonDown()) {
mouseHandler(
event, lnf->getUserInputAction(UserInput::left | UserInput::start |
UserInput::tracepoint));
}
}
if (m_mouse_drag_state == MouseDragState::none) {
m_mouse_drag_state = MouseDragState::start;
}
if (event.getNumberOfClicks() > 1) {
mouseHandler(event, lnf->getUserInputAction(UserInput::left |
UserInput::double_click |
UserInput::graph_area));
}
} }
void Plot::mouseDrag(const juce::MouseEvent& event) { if (isVisible()) { const auto lnf = getPlotLookAndFeel(); if (m_legend.get() == event.eventComponent) { mouseHandler(event, lnf->getUserInputAction(UserInput::left | UserInput::drag | UserInput::legend)); } else if (m_selected_area.get() == event.eventComponent && event.mouseWasDraggedSinceMouseDown() && event.getNumberOfClicks() == 1) { if (m_modifiers && m_modifiers->isCommandDown()) { mouseHandler(event, lnf->getUserInputAction( UserInput::left | UserInput::drag | UserInput::ctrl | UserInput::graph_area)); } else if (m_mouse_drag_state == MouseDragState::start) { mouseHandler(event, lnf->getUserInputAction( UserInput::left | UserInput::drag | UserInput::start | UserInput::graph_area)); m_mouse_drag_state = MouseDragState::drag; } else { mouseHandler(event, lnf->getUserInputAction(UserInput::left | UserInput::drag | UserInput::graph_area)); m_mouse_drag_state = MouseDragState::drag; } } else if (m_trace->isComponentTracePoint(event.eventComponent) && event.getNumberOfClicks() == 1) { mouseHandler(event, lnf->getUserInputAction(UserInput::left | UserInput::drag | UserInput::tracepoint)); } else if (m_trace->isComponentTraceLabel(event.eventComponent)) { mouseHandler(event, lnf->getUserInputAction(UserInput::left | UserInput::drag | UserInput::trace_label)); } } }
void Plot::mouseUp(const juce::MouseEvent& event) { if (isVisible()) { const auto lnf = getPlotLookAndFeel(); if (m_selected_area.get() == event.eventComponent && m_mouse_drag_state == MouseDragState::drag) { if (!event.mods.isRightButtonDown()) { mouseHandler(event, lnf->getUserInputAction( UserInput::left | UserInput::drag | UserInput::end | UserInput::graph_area)); }
m_mouse_drag_state = MouseDragState::none;
}
if (m_trace->isComponentTracePoint(event.eventComponent)) {
if (!event.mods.isRightButtonDown()) {
mouseHandler(event,
lnf->getUserInputAction(UserInput::left | UserInput::end |
UserInput::tracepoint));
}
}
} }
void Plot::modifierKeysChanged(const juce::ModifierKeys& modifiers) { m_modifiers = &modifiers; }
void Plot::setMovePointsType(const PixelPointMoveType move_points_type) { m_pixel_point_move_type = move_points_type; syncDownsamplingModeWithMoveType(); }
void Plot::addSelectableTracePoints() { if (m_pixel_point_move_type == PixelPointMoveType::none) return; m_trace->clear();
for (const auto& graph_line : *m_graph_lines) { const auto& y_values = graph_line->getYData(); size_t data_point_index = 0;
for (; data_point_index < y_values.size(); data_point_index++) {
m_trace->addTracePoint(
graph_line.get(), data_point_index,
TracePointVisibilityType::point_visible_when_selected);
}
}
m_trace->addAndMakeVisibleTo(this); }
void Plot::syncDownsamplingModeWithMoveType() { switch (m_pixel_point_move_type) { case PixelPointMoveType::none: return; break; case PixelPointMoveType::horizontal: this->setDownsamplingTypeInternal(DownsamplingType::no_downsampling); break; case PixelPointMoveType::vertical: this->setDownsamplingTypeInternal(DownsamplingType::no_downsampling); break; case PixelPointMoveType::horizontal_vertical: this->setDownsamplingTypeInternal(DownsamplingType::no_downsampling); break; } }
juce::Point
const auto mouse_pos =
(event.getPosition() + component_pos - m_graph_bounds->getPosition())
.toFloat();
return mouse_pos;
}
return event.getPosition().toFloat(); }
void Plot::setGraphLineDataChangedCallback( GraphLinesChangedCallback graph_lines_changed_callback) { m_graph_lines_changed_callback = graph_lines_changed_callback; }
void Plot::panning(const juce::MouseEvent& event) { const auto mouse_pos = getMousePositionRelativeToGraphArea(event); const auto d_mouse_pos = mouse_pos - m_prev_mouse_position; const auto new_x_lim_min_pixel = m_graph_bounds->getX() - d_mouse_pos.getX(); const auto new_x_lim_max_pixel = m_graph_bounds->getRight() - d_mouse_pos.getX(); const auto new_x_lim_data_min = getXDataFromXPixelCoordinate( new_x_lim_min_pixel, m_graph_bounds->toFloat(), m_x_lim, m_x_scaling); const auto new_x_lim_data_max = getXDataFromXPixelCoordinate( new_x_lim_max_pixel, m_graph_bounds->toFloat(), m_x_lim, m_x_scaling); const auto new_x_lim_data = Lim_f(new_x_lim_data_min, new_x_lim_data_max);
const auto new_y_lim_min_pos = m_graph_bounds->getBottom() - d_mouse_pos.getY(); const auto new_y_lim_max_pos = m_graph_bounds->getY() - d_mouse_pos.getY(); const auto new_y_lim_data_min = getYDataFromYPixelCoordinate( new_y_lim_min_pos, m_graph_bounds->toFloat(), m_y_lim, m_y_scaling); const auto new_y_lim_data_max = getYDataFromYPixelCoordinate( new_y_lim_max_pos, m_graph_bounds->toFloat(), m_y_lim, m_y_scaling); const auto new_y_lim_data = Lim_f(new_y_lim_data_min, new_y_lim_data_max);
m_prev_mouse_position = mouse_pos; m_is_panning_or_zoomed_active = true;
updateXLim(new_x_lim_data); updateYLim(new_y_lim_data);
repaint(); }
} // namespace cmp
/**
- Copyright (c) 2022 Frans Rosencrantz
- This software is released under the MIT License.
- https://opensource.org/licenses/MIT */
#include "cmp_downsampler.h"
#include
#include "cmp_datamodels.h" #include "cmp_utils.h"
namespace cmp { namespace { constexpr size_t MIN_POINTS_FOR_DOWNSAMPLING = 100u; constexpr size_t MAX_POINTS_PER_PIXEL = 3u; constexpr int PADDING_POINTS = 2; // Number of points to add outside visible range constexpr bool COMPUTE_ALL_POINTS = false;
template <class FloatType>
struct MinMaxIndices {
size_t min_idx;
size_t max_idx;
FloatType min_val;
FloatType max_val;
};
template<class FloatType>
MinMaxIndices<FloatType> findMinMaxIndices(const std::vector<FloatType>& y_data,
size_t start_idx,
size_t end_idx) {
MinMaxIndices<FloatType> result{start_idx, start_idx, y_data[start_idx], y_data[start_idx]};
for (auto idx = start_idx + 1; idx < end_idx; ++idx) {
auto y_value = y_data[idx];
if (y_value < result.min_val) {
result.min_val = y_value;
result.min_idx = idx;
} else if (y_value > result.max_val) {
result.max_val = y_value;
result.max_idx = idx;
}
}
return result;
}
template <class ValueType>
struct XRangeIndices {
size_t start_idx;
size_t end_idx;
};
template <class ValueType>
XRangeIndices<ValueType> findDataRange(const ValueType x_min_lim,
const ValueType x_max_lim,
const std::vector<ValueType>& x_data) {
if (x_data.empty()) {
return {0u, 0u};
}
// Find start index, ensuring we don't underflow
const auto raw_start = std::distance(x_data.begin(),
std::lower_bound(x_data.begin(), x_data.end(), x_min_lim));
const auto padded_start = std::max((const int)0L, (const int)(raw_start - PADDING_POINTS));
const auto start_idx = static_cast<size_t>(padded_start);
// Find end index, ensuring we don't overflow
const auto raw_end = std::distance(x_data.begin(),
std::upper_bound(x_data.begin(), x_data.end(), x_max_lim));
const auto max_size = static_cast<long>(x_data.size() - 1);
const auto padded_end = std::min((const int)max_size, (const int)(raw_end + PADDING_POINTS));
const auto end_idx = static_cast<size_t>(padded_end);
return XRangeIndices<ValueType>{start_idx, end_idx};
}
template <class ValueType>
bool shouldAddPoint(const ValueType current_value,
const ValueType last_value,
const ValueType min_distance,
const ValueType prev_difference,
const ValueType current_difference) {
// Add point if direction changes or distance exceeds threshold
const bool direction_changed = (std::signbit(prev_difference) !=
std::signbit(current_difference));
const bool exceeds_distance = (std::abs(last_value - current_value) >
min_distance);
return direction_changed || exceeds_distance;
}
template <class FloatType>
struct PixelColumnData {
std::vector<size_t> indices;
FloatType min_val;
FloatType max_val;
size_t min_idx;
size_t max_idx;
};
template <class FloatType>
void processPixelColumn(const std::vector<FloatType>& y_data,
size_t start_idx,
size_t end_idx,
fast_vector<std::size_t>& xy_indices) {
if (end_idx - start_idx <= MAX_POINTS_PER_PIXEL) {
// For small segments, include all points
for (auto i = start_idx; i < end_idx; ++i) {
xy_indices.push_back(i);
}
return;
}
// Find min/max points in this column
auto [min_idx, max_idx, min_val, max_val] =
findMinMaxIndices(y_data, start_idx, end_idx);
// Always include the start point
xy_indices.push_back(start_idx);
// Add min/max points in order of appearance
if (min_idx < max_idx) {
xy_indices.push_back_if_not_in_back(min_idx);
xy_indices.push_back_if_not_in_back(max_idx);
} else {
xy_indices.push_back_if_not_in_back(max_idx);
xy_indices.push_back_if_not_in_back(min_idx);
}
// Include end point if it's significant
if (end_idx - 1 != max_idx && end_idx - 1 != min_idx) {
xy_indices.push_back_if_not_in_back(end_idx - 1);
}
}
}
template <class ValueType> static auto computeXStartIdx(const ValueType x_min_lim, const std::vector<ValueType>& x_data) { int start_x_index{0};
// The points that are on top of each other or outside the graph area are // dicarded. for (const auto& x : x_data) { if (x >= x_min_lim) { int start_i_outside = 2; while (true) { if ((start_x_index - start_i_outside) >= 0) { // start_i_outside indices outside the left side to get rid of // artifacts. start_x_index = start_x_index - start_i_outside; break; } --start_i_outside; } break; } start_x_index++; }
start_x_index = start_x_index == x_data.size() ? x_data.size() - 1u : start_x_index;
return start_x_index; }
template <class ValueType> static auto computeXEndIdx(const ValueType x_max_lim, const std::vector<ValueType>& x_data) { int end_x_index{int(x_data.size() - 1)};
for (auto x = x_data.rbegin(); x != x_data.rend(); ++x) { if (*x <= x_max_lim || end_x_index == 0u) { auto start_i_outside = 2u; while (true) { if (end_x_index < x_data.size() - start_i_outside) { // Two indices outside the right side to get rid of artifacts. end_x_index = end_x_index + start_i_outside; break; } --start_i_outside; } break; } end_x_index--; }
return end_x_index; }
template <class ValueType, class IndexType, class ForwardIt>
static auto calculateXIndicesBetweenStartEnd(
const ForwardIt start_x_idx, const ForwardIt end_x_idx,
const Scaling x_scaling, const Lim<ValueType> x_lim, const juce::Rectangle
float last_added_x = x_data[start_x_idx]; std::size_t current_index = start_x_idx; std::size_t pixel_point_index{1u}; const auto inverse_x_scale = 1.0f / x_scale; auto last_x_diff = 0.f;
if (x_scaling == Scaling::linear) { for (auto i = current_index; i < end_x_idx; ++i) { const auto current_x_diff = i > 0 ? x_data[i - 1] - x_data[i] : 0.f;
// If the x values suddenly go opposite, we need to add that value.
const auto force_add_x =
(std::signbit(last_x_diff) != std::signbit(current_x_diff));
last_x_diff = current_x_diff;
if (force_add_x || (std::abs(last_added_x - x_data[i])) > inverse_x_scale) {
last_added_x = x_data[i];
x_based_idxs_out[pixel_point_index++] = i;
}
}
} else if (x_scaling == Scaling::logarithmic) { for (auto x = x_data.begin() + start_x_idx; x != x_data.begin() + end_x_idx; ++x) { if (std::log10(std::abs(*x / last_added_x)) > inverse_x_scale) { last_added_x = *x; x_based_idxs_out[pixel_point_index++] = current_index; } current_index++; } }
const auto x_idxs_size_required = pixel_point_index + 1; return x_idxs_size_required; }
template <class FloatType>
void Downsampler<FloatType>::calculateXIndices(
const Scaling x_scaling,
const Lim<FloatType> x_lim,
const juce::Rectangle
x_based_idxs_out.resize(x_data.size());
// Handle small datasets without downsampling
if (x_data.size() < MIN_POINTS_FOR_DOWNSAMPLING) {
std::iota(x_based_idxs_out.begin(), x_based_idxs_out.end(), 0u);
return;
}
// Find visible data range
const auto range = findDataRange(x_lim.min, x_lim.max, x_data);
// Set initial point
x_based_idxs_out[0] = range.start_idx;
// Calculate scale factors
const auto [scale, offset] = getXScaleAndOffset(
float(graph_bounds.getWidth()), x_lim, x_scaling);
const auto inverse_scale = 1.0f / scale;
size_t output_idx = 1;
float last_added_x = x_data[range.start_idx];
float last_diff = 0.f;
// Process points based on scaling type
if (x_scaling == Scaling::linear) {
for (size_t i = range.start_idx + 1; i < range.end_idx; ++i) {
const auto current_diff = i > 0 ? x_data[i - 1] - x_data[i] : 0.f;
if (shouldAddPoint(x_data[i], last_added_x, inverse_scale,
last_diff, current_diff)) {
last_added_x = x_data[i];
x_based_idxs_out[output_idx++] = i;
}
last_diff = current_diff;
}
} else if (x_scaling == Scaling::logarithmic) {
for (size_t i = range.start_idx + 1; i < range.end_idx; ++i) {
if (std::log10(std::abs(x_data[i] / last_added_x)) > inverse_scale) {
last_added_x = x_data[i];
x_based_idxs_out[output_idx++] = i;
}
}
}
// Add final point and resize
x_based_idxs_out[output_idx++] = range.end_idx;
x_based_idxs_out.resize(output_idx);
}
template <class FloatType> void Downsampler<FloatType>::calculateXYBasedIdxs( const std::vectorstd::size_t& x_indices, const std::vector<FloatType>& y_data, std::vectorstd::size_t& xy_indices_out) { if (x_indices.empty()) { xy_indices_out.clear(); return; }
fast_vector<std::size_t> xy_indices(xy_indices_out, y_data.size());
if (y_data.size() < MIN_POINTS_FOR_DOWNSAMPLING) {
xy_indices = x_indices;
xy_indices_out = xy_indices.get();
return;
}
// Process each segment between x-indices
for (auto i_it = x_indices.begin(); std::next(i_it) != x_indices.end(); ++i_it) {
const auto start_idx = *i_it;
const auto end_idx = *std::next(i_it);
processPixelColumn(y_data, start_idx, end_idx, xy_indices);
}
// Ensure the last point is included
if (!x_indices.empty()) {
xy_indices.push_back_if_not_in_back(x_indices.back());
}
// Transfer results back to output vector
xy_indices_out = xy_indices.get();
}
template class Downsampler
/**
- Copyright (c) 2022 Frans Rosencrantz
- This software is released under the MIT License.
- https://opensource.org/licenses/MIT */
#pragma once
#include
constexpr // This macro should be used in those cases where C++20 only
// allows it. Example: virtual CONSTEXPR20 int foo() = 0;
#else
#define CONSTEXPR20
#endif
#endif
#ifdef __has_cpp_attribute #if (__has_cpp_attribute(unlikely)) #define UNLIKELY [[unlikely]] #else #define UNLIKELY #endif #endif
#include
#include "juce_gui_basics/juce_gui_basics.h"
namespace cmp {
/============================================================================/
class GraphLine; class Grid; class Frame; class PlotLabel; class Legend; class Trace; class GraphArea; class PlotLookAndFeel; template <typename T> class Observable; template <typename T> class Observer;
struct LegendLabel; struct GraphAttribute; struct Marker; struct GraphSpread; struct GraphSpreadIndex; struct GridLine; struct GraphLineList; struct AreLabelsSet; struct CommonPlotParameterView; struct GraphLineDataView; template <class ValueType> struct Lim;
/============================================================================/
typedef std::vector<std::unique_ptr<GraphLine>> GraphLines;
typedef std::vector<juce::Point
/============================================================================/
/** Enum to define the scaling of an axis. / enum class Scaling : uint32_t { linear, /* Linear scaling of the graph line. / logarithmic /* Logarithmic scaling of the graph line. */ };
/** Enum to define the type of downsampling. / enum class DownsamplingType : uint32_t { no_downsampling, /* No downsampling. Slow when plotting alot of values. / x_downsampling, /* Downsampling only based on the x-values, makes sure that there is only one plotted value per x-pixel value. Fastest, but will discard x-values that are located with the same x-pixel value near each other. Recommended for real-time plotting. / xy_downsampling, /* Skips x- & y-values that shares the same pixel on the screen. It's quicker than 'no_downsampling' but slower than 'x_downsampling'. */ };
enum class UserInput : uint64_t { // Mouse button pressed left = 1UL, right = 1UL << 1, middle = 1UL << 2,
// Type of press end = 1UL << 16, start = 1UL << 17, drag = 1UL << 18, double_click = 1UL << 19, scroll_up = 1UL << 20, scroll_down = 1UL << 21,
// Keyboard button shift = 1ULL << 32, ctrl = 1ULL << 33, alt = 1ULL << 34,
// Event component graph_area = 1ULL << 46, legend = 1ULL << 47, tracepoint = 1ULL << 48, trace_label = 1ULL << 49,
};
constexpr enum UserInput operator|(const enum UserInput selfValue, const enum UserInput inValue) { return (enum UserInput)(uint64_t(selfValue) | uint64_t(inValue)); }
/** Enum to define a type of action that will occur for a input. / enum class UserInputAction : uint32_t { /* Tracepoint related actions. / create_tracepoint, /* Creates a tracepoint. / move_tracepoint_to_closest_point, /* Move a tracepoint to closest point to the mouse. / move_tracepoint_label, /* Move a tracepoint label. / move_selected_trace_points, /* Move a pixel point. / select_tracepoint, /* Selecting a tracepoint. / select_tracepoints_within_selected_area, /* Selecting multiple tracepoints. / deselect_tracepoint, /* Deselecting a tracepoint. */
/** Zoom related actions. / zoom_selected_area, /* Zoom in on selected area. / zoom_in, /* Zoom in. / zoom_out, /* Zoom out. / zoom_reset, /* Reset the zoom. */
/** Selection area related actions. / select_area_start, /* Set start positon for selected area. / select_area_draw, /* Set end position of selected are and draw the area. */
/** Pixel point related actions. / create_movable_pixel_point, /* Create a movable pixel point. / remove_movable_pixel_point, /* Remove a pixel point. */
/** Legend related actions. / move_legend, /* Move a legend. */
/** Panning / panning, /* Panning. */
/** No action / none /* No action. */ };
/** Enum to define if the mouse has just start currently dragging or does not
- drag. / enum class MouseDragState : uint32_t { start, /* Start of a mouse drag. / drag, /* Mouse is currently dragging. / none /* No drag state. */ };
/** Enum to define when the tracepoint/Label should be visible or not. / enum class TracePointVisibilityType : uint32_t { /* Tracepoint and label is not visible. / not_visible, /* Tracepoint is visible only when selected. Tracelabel is not visible. / point_visible_when_selected, /* Tracepoint and label are visible when selected. / point_label_visible_when_selected, /* Tracepoint is always visible. */ visible, };
/** Enum to define how a pixel point can be moved / enum class PixelPointMoveType : uint32_t { /* Pixel point can't be moved. / none, /* Pixel point can be moved horizontally. / horizontal, /* Pixel point can be moved vertically. / vertical, /* Pixel point can be moved horizontally and vertically. */ horizontal_vertical, };
/** Enum to define what type of GraphLine. / enum class GraphLineType : uint32_t { /* GraphLine is any type. / any, /* GraphLine is a normal graph line. / normal, /* GraphLine is a horizontal line. / horizontal, /* GraphLine is a vertical line. */ vertical, };
/** Enum to define if grid should be drawn or if the grid should be small / enum class GridType : uint32_t { /* No grid is drawn. / none, /* Grid is drawn. / grid, /* Grid is drawn but with a smaller grid spacing. / tiny_grid, /* Grid drawn with translucent lines between. / grid_translucent, /* Tiny grid drawn with translucent lines between. */ tiny_grid_translucent, };
/** Enum to define which type of value to be observed. */ enum class ObserverId : uint32_t { Undefined, GraphBounds, XLim, YLim, XScaling, YScaling, DownsamplingType, };
/============================================================================/
/** @brief A template struct that defines min and max. */ template <class ValueType> struct Lim { constexpr Lim() : min{0}, max{0} {};
constexpr Lim(ValueType new_min, ValueType new_max) : min{new_min}, max{new_max} {}
constexpr Lim(const juce::Point<ValueType> lim) : min{lim.getX()}, max{lim.getY()} {}
ValueType min; ValueType max;
constexpr Lim<ValueType>& operator/=(const ValueType val) { min /= val; max /= val;
return *this;
}
constexpr Lim<ValueType>& operator=(const Lim<ValueType> rhs) { min = rhs.min; max = rhs.max;
return *this;
}
bool operator==(const Lim<ValueType>& rhs) const noexcept { return min == rhs.min && max == rhs.max; }
bool operator!=(const Lim<ValueType>& rhs) const noexcept { return min != rhs.min || max != rhs.max; }
constexpr explicit operator bool() const noexcept { constexpr auto zero = static_cast<ValueType>(0);
return max != zero || min != zero;
}
constexpr bool isMinOrMaxZero() const noexcept { constexpr auto zero = static_cast<ValueType>(0);
return max == zero || min == zero;
}
constexpr Lim<ValueType> operator+(const ValueType val) { this->min += val; this->max += val;
return *this;
}
constexpr Lim<ValueType> operator+=(const ValueType val) { this->min += val; this->max += val;
return *this;
} };
template <class ValueType> constexpr Lim<ValueType> operator/(const Lim<ValueType>& rhs, const ValueType val) { Lim<ValueType> new_lim;
new_lim.min = rhs.min / val; new_lim.max = rhs.max / val;
return new_lim; };
template <class ValueType> constexpr Lim<ValueType> operator+(const Lim<ValueType>& rhs, const ValueType val) { Lim<ValueType> new_lim;
new_lim.min += val; new_lim.max += val;
return new_lim; };
/** @brief A struct that defines min and max using float. */
typedef Lim
/** @brief A view of some common plot parameters. */
struct CommonPlotParameterView {
CommonPlotParameterView(const juce::Rectangle
struct Marker { /** Type of marker. */ enum class Type { Circle, Pentagram, Square, UpTriangle, RightTriangle, DownTriangle, LeftTriangle } type;
/** Contructor marker type only. */ Marker(const Marker::Type t) : type{t} {};
/** Marker outline color. */ std::optionaljuce::Colour EdgeColour;
/** Marker interior color. */ std::optionaljuce::Colour FaceColour;
/** PathStrokeType used when drawing the edge line of the marker. */ juce::PathStrokeType edge_stroke_type{ 1.0f, juce::PathStrokeType::JointStyle::mitered, juce::PathStrokeType::EndCapStyle::rounded};
static juce::Path getMarkerPathFrom(Marker marker, const float length) { juce::Path path;
auto addUpTriangleTo = [length](auto& path) {
path.addTriangle({0.0f, -length / 2.0f}, {-length / 2.0f, length / 2.0f},
{length / 2.0f, length / 2.0f});
};
switch (marker.type) {
case Marker::Type::Circle:
path.addEllipse({-length / 2.0f, -length / 2.0f, length, length});
break;
case Marker::Type::Pentagram:
path.addStar({0, 0}, 5, length / 4.0f, length / 2.0f);
break;
case Marker::Type::Square:
path.addRectangle(-length / 2.0f, -length / 2.0f, length, length);
break;
case Marker::Type::UpTriangle:
addUpTriangleTo(path);
break;
case Marker::Type::RightTriangle:
addUpTriangleTo(path);
path.applyTransform(juce::AffineTransform::rotation(
juce::MathConstants<float>::pi / 2.0f, 0.0f, 0.0f));
break;
case Marker::Type::DownTriangle:
addUpTriangleTo(path);
path.applyTransform(juce::AffineTransform::rotation(
juce::MathConstants<float>::pi, 0.0f, 0.0f));
break;
case Marker::Type::LeftTriangle:
addUpTriangleTo(path);
path.applyTransform(juce::AffineTransform::rotation(
juce::MathConstants<float>::pi * 3.0f / 2.0f, 0.0f, 0.0f));
break;
default:
break;
}
return path;
} };
/** Attributes of a single graph. / struct GraphAttribute { /* Colour of the graph_line. */ std::optionaljuce::Colour graph_colour;
/** Custom path stroke @see juce::PathStrokeType */ std::optionaljuce::PathStrokeType path_stroke_type;
/** Use dash_lengths to draw dashed graph_line. E.g. dashed_lengths = {2,
- 2, 4, 6} will draw a line of 2 pixels, skip 2 pixels, draw 3 pixels, skip
- 6 pixels, and then repeat. */
std::optional<std::vector
> dashed_lengths;
/** Set the opacity of the graph_line. Value must be between 0 (transparent)
- and 1.0 (opaque). */
std::optional
graph_line_opacity;
/** The type of marker drawn on each pixel point. */ std::optionalcmp::Marker marker;
/** Callback function which is triggerd for every plotted pixel_point. E.g.
- Can be used to do custom plot markers for each pixel_point.*/
std::function<void(juce::Graphics& g, juce::Point
data_point, juce::Point pixel_point)> on_pixel_point_paint{nullptr};
/** Creates a vertical linear gradient between top and bottom of the graph
- area. Only the gradient below the graph line is visible. */ std::optional<std::pair<juce::Colour, juce::Colour>> gradient_colours; };
/** @brief A struct that defines between which two graph_lines the area is
- filled. */ struct GraphSpreadIndex { std::size_t first_graph; std::size_t second_graph; };
/** @brief A view of the data required to draw a graph_line */
struct GraphLineDataView {
GraphLineDataView(const std::vector
GraphLineDataView(const GraphLine& graph_line);
GraphLineDataView(const std::vector
const std::vector
/**
- @brief A struct that defines a vector with fast push_back.
- This struct is used to speed up the downsampling process.
- The push_back_fast method is about 20x - 30x faster than
- std::vector::push_back.
- Example usage:
- @code{.cpp}
- std::vector
v; - fast_vector
fv(v, 50'000); - for (int i = 0; i < 50'000; i++)
-
fv.push_back(i); - @endcode
- @warning This will only work if the vector is resized with a large size
- before resized to the correct size with resize_with_fast_push_back_size.
- @tparam T The type of elements stored in the vector.
/
template <typename T>
struct fast_vector {
/*
- @brief Constructs a new fast vector object.
- This constructor initializes the vector with the given vector and resizes
- it to the specified size.
- @param v The vector to initialize with.
- @param size The size to resize the vector to. */ constexpr fast_vector(std::vector<T>& v, std::size_t size) : vec(v) { vec.resize(size); }
/**
- @brief Destroys the fast vector object.
- Before the object is destroyed, the vector is resized using the
- resize_with_fast_push_back_size function. */ ~fast_vector() { resize_with_fast_push_back_size(); }
/**
- @brief Fast push_back for the vector.
- @warning This will only work if the vector has a size already.
- @param elem The element to be added to the vector. */ constexpr void push_back(const T elem) noexcept { vec[index++] = elem; }
/**
- @brief push_back for the vector if the element is not in back of the
- vector.
- @warning This will only work if the vector has a size already.
- @param elem The element to be added to the vector. */ constexpr void push_back_if_not_in_back(const T elem) noexcept { if (vec.back() != elem) vec[index++] = elem; }
fast_vector& operator=(const std::vector<T>& v) { vec = v; index = vec.size(); return
Thanks, you can try this:
- Fork the repo and add the repo as remote
- Add you changes to a new branch
- Push the branch to your remote fork
- In Github create a PR from your branch in your fork to my repo