CustomMatPlot icon indicating copy to clipboard operation
CustomMatPlot copied to clipboard

VisualStudio 2022 C++20 errors

Open martin-mm opened this issue 10 months ago • 4 comments

  1. 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));
  2. Class Observable is missing the == Operator: add bool operator==(const T& Val) { return Val == value; }

martin-mm avatar Mar 08 '25 12:03 martin-mm

Thanks, can you fix it in pr?

franshej avatar Mar 08 '25 16:03 franshej

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

martin-mm avatar Mar 08 '25 17:03 martin-mm

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 #include #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::max(); auto min_value = std::numeric_limits::max();

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 std::tuple<size_t, const GraphLine*> Plot::findNearestPoint( juce::Point point, const GraphLine* graphline) { auto closest_point = juce::Point(std::numeric_limits::max(), std::numeric_limits::max());

juce::Point closest_data_point, current_point, current_data_point; size_t data_point_index{0}; size_t closest_data_point_index{0};

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::epsilon())) { m_x_lim = {new_x_lim.min - 1, new_x_lim.max + 1}; }

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::epsilon()) { m_y_lim = {new_y_lim.min - 1, new_y_lim.max + 1}; }

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& y_coordinates, const GraphAttributeList& graph_attributes) { auto [x_data, y_data] = prepareDataForVerticalOrHorizontalLines(y_coordinates, m_x_lim); if (x_data.empty() || y_data.empty()) return;

plotInternalGraphLineType::horizontal(y_data, x_data, graph_attributes); }

void Plot::plotVerticalLines(const std::vector& x_coordinates, const GraphAttributeList& graph_attributes) { auto [y_data, x_data] = prepareDataForVerticalOrHorizontalLines(x_coordinates, m_y_lim); if (y_data.empty() || x_data.empty()) return;

plotInternalGraphLineType::vertical(y_data, x_data, graph_attributes); }

template <GraphLineType t_graph_line_type> void Plot::plotInternal(const std::vector<std::vector>& y_data, const std::vector<std::vector>& x_data, const GraphAttributeList& graph_attributes, const bool update_y_data_only) { if (update_y_data_only) jassert(!m_graph_lines->empty());

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> Plot::generateXdataRamp( const std::vector<std::vector>& y_data) { auto generateRamp = [&] { std::vector<std::vector> x_data(y_data.size()); auto x_graph_it = std::begin(x_data); for (const auto& y_graph : y_data) { (*x_graph_it).resize(y_graph.size()); std::iota(x_graph_it->begin(), x_graph_it->end(), 1.0f); ++x_graph_it; } return x_data; };

return generateRamp(); }

void Plot::plot(const std::vector<std::vector>& y_data, const std::vector<std::vector>& x_data, const GraphAttributeList& graph_attributes) { plotInternalGraphLineType::normal(y_data, x_data, graph_attributes); repaint(); }

void Plot::plotUpdateYOnly(const std::vector<std::vector>& y_data) { plotInternalGraphLineType::normal(y_data, {}, {}, true); repaint(m_graph_bounds); }

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& x_ticks) { m_grid->setXTicks(x_ticks); }

void Plot::setYTicks(const std::vector& y_ticks) { m_grid->setYTicks(y_ticks); }

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& trace_point_coordinate) { this->setTracePointInternal(trace_point_coordinate, true); }

// Set trace point internal void Plot::setTracePointInternal( const juce::Point& trace_point_coordinate, bool is_point_data_point) { const auto [data_point_index, nearest_graph_line] = is_point_data_point ? findNearestPoint(trace_point_coordinate) : findNearestPoint(trace_point_coordinate);

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>& y_data, const GraphAttributeList& graph_attribute_list) { if (y_data.empty()) return;

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>& y_data, const GraphAttributeList& graph_attribute_list);

template <GraphLineType t_graph_line_type> void Plot::updateGraphLineXData(const std::vector<std::vector>& x_data) { // There is a bug in the code if this assert happens. jassert(x_data.size() == m_graph_lines->size<t_graph_line_type>());

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(mouse_pos, nullptr);

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& start_position) { for (auto& trace_point : m_trace->getTraceLabelPoints()) { m_trace->selectTracePoint(trace_point.trace_point.get(), false); }

m_selected_area->setStartPosition(start_position); }

void Plot::drawSelectedRegion(const juce::Point& end_position) { m_selected_area->setEndPosition(end_position); m_selected_area->repaint(); }

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(); constexpr auto margin = juce::Point(2, 5);

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 Plot::getMousePositionRelativeToGraphArea( const juce::MouseEvent& event) const { if (event.eventComponent != m_selected_area.get()) { const auto component_pos = event.eventComponent->getBounds().getPosition();

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

#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 &graph_bounds, const std::vector<ValueType>& x_data, std::vector<IndexType>& x_based_idxs_out) { const auto [x_scale, x_offset] = getXScaleAndOffset(float(graph_bounds.getWidth()), x_lim, x_scaling);

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& graph_bounds, const std::vector<FloatType>& x_data, std::vectorstd::size_t& x_based_idxs_out) { if (x_data.empty()) { x_based_idxs_out.clear(); return; }

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; } // namespace cmp

/**

  • Copyright (c) 2022 Frans Rosencrantz
  • This software is released under the MIT License.
  • https://opensource.org/licenses/MIT */

#pragma once

#include #ifdef __cpp_constexpr #if __cpp_constexpr >= 201907L #define CONSTEXPR20
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 #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> PixelPoints; typedef std::vector<GraphLineDataView> GraphLineDataViewList; typedef std::pair<std::string, juce::Rectangle> Label; typedef std::vector<Label> LabelVector; typedef std::vectorstd::string StringVector; typedef std::vectorjuce::Colour ColourVector; typedef std::vector<GraphAttribute> GraphAttributeList; typedef std::vector<std::unique_ptr<GraphSpread>> GraphSpreadList; typedef Lim Lim_f; // Callback function for when a graph line is changed. E.g. when it is changed // when a pixel point is moved. void GraphLinesChangedCallback(const // "GraphLineDataViewList &graph_line) { ... };"" typedef std::function<void(const GraphLineDataViewList& graph_line)> GraphLinesChangedCallback;

/============================================================================/

/** 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 Lim_f;

/** @brief A view of some common plot parameters. */ struct CommonPlotParameterView { CommonPlotParameterView(const juce::Rectangle& gb, const Lim_f& xl, const Lim_f& yl, const Scaling& xs, const Scaling& ys, const DownsamplingType& ds) : graph_bounds{gb}, x_lim{xl}, y_lim{yl}, x_scaling{xs}, y_scaling{ys}, downsampling_type{ds} {}; CommonPlotParameterView(const juce::Rectangle&&, const Lim_f&&, const Lim_f&&, const Scaling&&, const Scaling&&, const DownsamplingType&&) = delete; // prevents rvalue binding const juce::Rectangle& graph_bounds; const Lim_f &x_lim, &y_lim; const Scaling &x_scaling, &y_scaling; const DownsamplingType& downsampling_type; };

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& _x_data, const std::vector& _y_data, const PixelPoints& _pixel_points, const std::vectorstd::size_t& _pixel_point_indices, const GraphAttribute& _graph_attribute);

GraphLineDataView(const GraphLine& graph_line); GraphLineDataView(const std::vector&&, const std::vector&&, const PixelPoints&&, const std::vectorstd::size_t&&) = delete; // prevents rvalue binding

const std::vector&x_data, &y_data; const PixelPoints& pixel_points; const std::vectorstd::size_t& pixel_point_indices; const GraphAttribute& graph_attribute; };

/**

  • @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

martin-mm avatar Mar 08 '25 17:03 martin-mm

Thanks, you can try this:

  1. Fork the repo and add the repo as remote
  2. Add you changes to a new branch
  3. Push the branch to your remote fork
  4. In Github create a PR from your branch in your fork to my repo

franshej avatar Mar 08 '25 20:03 franshej