2-D Plotting component

Hello all,

I have been working on a plotting component during my parental leave for some of my upcoming projects. I hope someone else might be interested in using it. Link to the repo: GitHub - franshej/CustomMatPlot: Plot library for the JUCE Framework

Here are some of the features:

  • Axis scaling: Linear and logarithmic.
  • Customizable view using the lookandfeel class.
  • Legend.
  • Zoom.
  • Tracepoints.
  • Fill area between two graphs.
  • Axis labels.
  • Ticks and Tick-labels.
  • Grids and tiny grids.
  • Markers: Star, triangles, square etc.
  • Custom stroke path.
  • Callback for every visible data point.
  • Callback for tace points.
  • Two different downsampler levels

Best regards
Frans

20 Likes

Very nice looking project! Thanks for sharing!

1 Like

Yes very nice indeed :slight_smile:

How does it fair with realtime use?

1 Like

Thanks for the kind words

It may not be the best solution for real-time, but it works pretty well. There are two different types of downsampling modes. The first mode only looks at the given x-data and calculates which points need to be drawn, so there’s only one point drawn per column. In some real-time cases, we may only need to update the x-data ones, which means we only have to do this analysis once (And every time we resize or zoom in/out). After this analysis, we only need to update the y-data in real-time and calculate the new y-coordinate for the points we calculated earlier in the x-data analysis. The downside of this solution is that we discard points sharing the same column and lose accuracy in the plot. However, it doesn’t matter in some cases since the data is already smoothed. For example, a real-time frequency plot, where the x_data is static and y_data dynamic. Below is a short example:

// Setting the downsample type in the constructor. And deactivate auto lims.
Constructor() {
  m_plot.setDownsamplingType(cmp::DownsamplingType::x_downsaming);
  
  // Setting lims to deactivate the auto lims. 
  m_plot.xLim(1.0f, 10000.0f);
  m_plot.yLim(-40.0f, 10.0f);
}

// Setting the x_data
void prepareToPlay(int /*samplesPerBlockExpected*/,
                               double new_sample_rate) override {
  const auto x_data = createXDataFrom(new_sample_rate);

  // Setting the x_data once. Or.. every time we change sample_rate in this case.
  m_plot.plot(dummy_y_data, x_data);
}

// Only updates the y_data in cb.
void updateYDataCallBack(){
  m_plot->realtimePlot(new_y_data);
}

I’ve also updated the real-time plot example using the x_downsample: GitHub - franshej/Realtime-Frequency-Plot-Example: This is an example app for the CustomMatPlot Library.

The second downsampling mode analyses both the x/y-data to find the min/max points sharing the same column. This mode is, of course, slower than the first but you don’t lose any accuracy. This is also the default mode.

If you have any ideas on how can improve the real-time plot, please share :slight_smile:

2 Likes

Looks fantastic! Congratulations, and thanks for sharing :slight_smile:

1 Like

Looks great!

PS: I wish I had had that much free time during my own parental leave :laughing:

3 Likes

Hi Everyone! :),

I’v been trying to get CMP to display a graph that I like,
but have run into some problems…

I’ve read all the source code for the examples and documentation,
but can’t find what I’m looking for…

here’s my current (temporary) code to generate my plot graph:

PlotComponent::PlotComponent()
{
m_plot.setBounds(getLocalBounds());

// Add the plot object as a child component.
addAndMakeVisible(m_plot);

// Create some data to visulize.
std::vector<std::vector<float>> y_data;

y_data.push_back(cmp::generateUniformRandomVector(80, -1.0f, +3.0f));
y_data.push_back(cmp::generateUniformRandomVector(80, -3.0f, +3.0f));

// Setting new colours on graph one and two.
auto graph_attributes = cmp::GraphAttributeList(y_data.size());
graph_attributes[0].graph_colour = Colour(255, 203, 4);
graph_attributes[1].graph_colour = Colour(255, 51, 154);;
graph_attributes[0].marker = cmp::Marker::Type::Circle;

cmp::PlotLookAndFeel* laf = new CustomLookAndFeel();

graph_attributes[0].path_stroke_type = juce::PathStrokeType(1.0f);

m_plot.setLookAndFeel(laf);

// Plot some values.
m_plot.plot(y_data, {}, graph_attributes);

m_plot.setYTicks({ -5.f, -4.f, -3.f, -2.f, -1.f, 0.f, 1.f, 2.f, 3.f, 4.f, 5.f });

}

If you look at the attached image of my plot, you’ll see I have the following issues:

  • I enabled ticks on the Y axis and they don’t show up on the plot, only the values do. (Like in the picture “nice looking fills between graph lines” on the CMP github page.
  • how do I have the plot generate horizontal and/or vertical lines for the whole plotting area that correspond to the X and Y axii numbers/labels… (like in the 2nd picture of the “some good looking sines!” on the CMP github page.
  • how can I make the circles from cmp::Marker::Type::Circle smaller ?
  • how can I move the bottom X values to the top of the graph component ?
  • how can I change the background, line and x/y text colours ?
  • Can anyone explain what the downsampling does ?
  • How can I create spline/paths instead of just linear lines between the datapoints ? (Like in the 2nd picture “some good looking sines!” on the CMP github page.

Yours help would be greatly appreciated as I’m developing an awesome, complex JUCE application and I really need a good graph plotting component…

Finally, Franshej → could you contact me as I would like to pay you to add a few small features to CMP like scrolling/panning etc… If you’re interested in making some extra money in your spare time, please contact me on terrence dot vergauwen at gmail dot com, thanks !!! :slight_smile:

Hello @radiance32,
Thanks for reaching out! I currently have minimal spare time since I’m moving on the 7 of July and getting my second child on the 19 of July. However, I will try to answer the questions below and help you.

  1. I don’t understand, from the code you posted I see that you set m_plot.setYTicks({ -5...
    did you also set the m_plot.setYTickLabels( ?
  2. Do you mean grid? try m_plot.gridON(true, false); or m_plot.gridON(true, true); for tiny grids.
  3. In your CustomLookAndFeel class you need to override getMarkerLength() to return the diameter you want on your circles. The default is 20px. Example:
class CustomLookAndFeel : public cmp::PlotLookAndFeel {
  std::size_t getMarkerLength() const noexcept override { return 10u; };
};
  1. If you check out the latest commit, you can override isXAxisLabelsBelowGraph() to return false in your PlotLookAndFeel class, example:
class CustomLookAndFeel : public cmp::PlotLookAndFeel {
  std::size_t getMarkerLength() const noexcept override { return 10u; };
  bool isXAxisLabelsBelowGraph() const noexcept override { return false; }
};
  1. I just saw a bug I need to fix to change the colours from the component. Otherwise, you can change the colour directly in the setDefaultPlotColours function in cmp_lookandfeel.cpp.
  2. “Can anyone explain what the downsampling does ?” I will explain this later when I have time :slight_smile:
  3. You need to use some third-party algorithm to create the x/y spline values and then plot them using this lib. c++ cubic spline interpolation library looks to be only a header file you need to include. However, It would be cool to have it directly in the lib :slight_smile:

"Finally, Franshej → could you… "
Sure :slight_smile: I will probably be able to implement those features during the summer. I send you an email.

Best regards,
Frans