Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for other dataset types #104

Closed
electromaggot opened this issue Aug 31, 2020 · 35 comments
Closed

Support for other dataset types #104

electromaggot opened this issue Aug 31, 2020 · 35 comments

Comments

@electromaggot
Copy link

I wish I could use ImPlot for audio data, which e.g. Spotify delivers as Int16 (signed) interleaved samples, which feed to the audio device as same. My FFTs yield integer ranges as well. I'd rather avoid converting entire blocks of ints to floats for trivial display, simply to skirt a library inflexibility.

ImPlot's core piece - the data it operates on and that data's type - being limited to floating point is a fundamental inconvenience. @ocornut's suggestion to templatize the datatype internally, then expose type-specific APIs, seemed to be summarily dismissed. #36 (comment)

@epezent you mentioned there that templates "make the header a mess" but the dual "float then double" repeated function prototypes don't seem elegant either.
Does anyone else have need for different dataset types? Surely someone has. I'm considering a fork to investigate a templated approach but would rather not repeat someone else's failed attempt at wheel reinvention. :-)

By the way, @epezent, nice work on this! It looks great.

@epezent
Copy link
Owner

epezent commented Aug 31, 2020

Tadd, first thanks for the compliments and suggestions! I agree, neither approach is perfect. However, we do use templates internally per @ocornut's suggestion. For example:

implot.h

void PlotLine(const char* label_id, const float* values, int count, int offset = 0, int stride = sizeof(float));
void PlotLine(const char* label_id, const double* values, int count, int offset = 0, int stride = sizeof(double));

implot_items.cpp

template <typename Getter>
inline void PlotEx(const char* label_id, Getter getter) {
    // a whole bunch of templated function calls inside of here
    ...
}

void PlotLine(const char* label_id, const float* values, int count, int offset, int stride) {
    GetterYs<float> getter(values,count,offset,stride);
    PlotEx(label_id, getter);
}

void PlotLine(const char* label_id, const double* values, int count, int offset, int stride) {
    GetterYs<double> getter(values,count,offset,stride);
    PlotEx(label_id, getter);
}

Supporting int16 should be trivial. As long as the type can cast to a double, you should be able to change the Getter template parameter and make the appropriate wrapper signature:

void PlotLine(const char* label_id, const short* values, int count, int offset, int stride) {
    GetterYs<short> getter(values,count,offset,stride);
    PlotEx(label_id, getter);
}

As far as supporting integral types in implot.h, I'm just not sure of the best way to proceed here. There are far to many possibilities to explicitly add them all. I suppose we could add an implot.inl file for the template code, but I'm not sure what complications this would bring about for our wrapper maintainers and how it would mesh with the ImGui philosophy.

ImGui does something like this for types that are not float:

bool DragScalar(const char* label, ImGuiDataType data_type, void* p_data, float v_speed, const void* p_min = NULL, const void* p_max = NULL, const char* format = NULL, float power = 1.0f);

where:

enum ImGuiDataType_
{
    ImGuiDataType_S8,       // signed char / char (with sensible compilers)
    ImGuiDataType_U8,       // unsigned char
    ImGuiDataType_S16,      // short
    ImGuiDataType_U16,      // unsigned short
    ImGuiDataType_S32,      // int
    ImGuiDataType_U32,      // unsigned int
    ImGuiDataType_S64,      // long long / __int64
    ImGuiDataType_U64,      // unsigned long long / unsigned __int64
    ImGuiDataType_Float,    // float
    ImGuiDataType_Double,   // double
    ImGuiDataType_COUNT
};

We could potentially do something similar, and leverage this enum. I'm happy to start a discussion about how we can make this more generalized and usable for everyone! Let me know your thoughts, and if @ocornut wants to chime in, that'd be really helpful too!

@epezent
Copy link
Owner

epezent commented Aug 31, 2020

Also, @electromaggot, note that you have

void PlotLine(const char* label_id, ImPlotPoint (*getter)(void* data, int idx), void* data, int count, int offset = 0);

at your disposal. You can write a custom getter function that will convert your data to the ImPlotPoint type. See the Custom Getters section of the demo. Regarding conversion: regardless of whether you use the function pointer getter or a templated getter, the data MUST be converted to double before it can be plotted. There's simply no way around this.

@epezent
Copy link
Owner

epezent commented Aug 31, 2020

As a proof of concept using ImGuiDataType, the following compiles with a minor correction (92f7879) to GetterYs:

void PlotLine(const char* label_id, const void* values, int count, ImGuiDataType type, int offset, int stride) {
    if      (type == ImGuiDataType_S8)     {  GetterYs<ImS8  >  getter((ImS8  *)values,count,offset,stride); PlotEx(label_id, getter); }
    else if (type == ImGuiDataType_U8)     {  GetterYs<ImU8  >  getter((ImU8  *)values,count,offset,stride); PlotEx(label_id, getter); }        
    else if (type == ImGuiDataType_S16)    {  GetterYs<ImS16 >  getter((ImS16 *)values,count,offset,stride); PlotEx(label_id, getter); }        
    else if (type == ImGuiDataType_U16)    {  GetterYs<ImU16 >  getter((ImU16 *)values,count,offset,stride); PlotEx(label_id, getter); }        
    else if (type == ImGuiDataType_S32)    {  GetterYs<ImS32 >  getter((ImS32 *)values,count,offset,stride); PlotEx(label_id, getter); }        
    else if (type == ImGuiDataType_U32)    {  GetterYs<ImU32 >  getter((ImU32 *)values,count,offset,stride); PlotEx(label_id, getter); }        
    else if (type == ImGuiDataType_S64)    {  GetterYs<ImS64 >  getter((ImS64 *)values,count,offset,stride); PlotEx(label_id, getter); }        
    else if (type == ImGuiDataType_U64)    {  GetterYs<ImU64 >  getter((ImU64 *)values,count,offset,stride); PlotEx(label_id, getter); }        
    else if (type == ImGuiDataType_Float)  {  GetterYs<float >  getter((float *)values,count,offset,stride); PlotEx(label_id, getter); }        
    else if (type == ImGuiDataType_Double) {  GetterYs<double>  getter((double*)values,count,offset,stride); PlotEx(label_id, getter); }        
}

It goes without saying that this will increase the overall compile size considerably once each plotting function is done this way.

@epezent
Copy link
Owner

epezent commented Aug 31, 2020

If we go this route, then each function in our public API may expose:

  1. a default version (either float or double -- ImGui uses float, but internally ImPlot uses double, so I'm not sure which is best to choose)
  2. a generic ImGuiDataType version
  3. a function pointer getter version

The alternative is to expose the template code in an inline file, and provide an entirely template API. Again, I don't know what issues this could create for our wrapper maintainers and ports. The public headers of both ImPlot and ImGui do expose templated objects (e.g. ImVector), but in ImGui none of the GUI functions themselves are templated, so my preference would be to do the same.

@electromaggot
Copy link
Author

Wow, @epezent! Excellent, helpful response and thanks for setting me straight (I should have dug-in more before posting, your patience Evan is appreciated).
I will chew on this and get implemented into my app tonight. I'll post how it works out for me and we can close this issue, but it's still great info that maybe can help someone else. Thanks again.

@epezent
Copy link
Owner

epezent commented Aug 31, 2020

No problem!

Let's leave this issue open and see if anyone else has input. I'm generally curious what data types people are using or wanting to use ImPlot for.

@epezent
Copy link
Owner

epezent commented Sep 2, 2020

Another possibility is this:

implot.h

template <typename T> 
void PlotLineT(const char* label_id, const T* xs, const T* ys, int count, int offset = 0, int stride = sizeof(T));

implot_items.cpp

template <typename T> 
void PlotLineT(const char* label_id, const T* xs, const T* ys, int count, int offset, int stride) {
    GetterXsYs<T> getter(xs,ys,count,offset,stride);
    return PlotLineEx(label_id, getter);
}

template void PlotLineT<ImS8>(const char* label_id, const ImS8* xs, const ImS8* ys, int count, int offset, int stride);
template void PlotLineT<ImU8>(const char* label_id, const ImU8* xs, const ImU8* ys, int count, int offset, int stride);
template void PlotLineT<ImS16>(const char* label_id, const ImS16* xs, const ImS16* ys, int count, int offset, int stride);
template void PlotLineT<ImU16>(const char* label_id, const ImU16* xs, const ImU16* ys, int count, int offset, int stride);
template void PlotLineT<ImS32>(const char* label_id, const ImS32* xs, const ImS32* ys, int count, int offset, int stride);
template void PlotLineT<ImU32>(const char* label_id, const ImU32* xs, const ImU32* ys, int count, int offset, int stride);
template void PlotLineT<ImS64>(const char* label_id, const ImS64* xs, const ImS64* ys, int count, int offset, int stride);
template void PlotLineT<ImU64>(const char* label_id, const ImU64* xs, const ImU64* ys, int count, int offset, int stride);
template void PlotLineT<float>(const char* label_id, const float* xs, const float* ys, int count, int offset, int stride);
template void PlotLineT<double>(const char* label_id, const double* xs, const double* ys, int count, int offset, int stride);

It compiles and links under both GCC and MSVC for me. I can call PlotLinesT with any of types above from my own code. If a user tries a type other than those, they'll get a linker error.

I honestly like this approach the most -- it's more terse, keeps the header clean of template implementation and redundant declarations, and the avoids branching seen above. My only reservations are due to increased compile size (meh, not a big deal) and introducing a templated API (well, sort of). I don't have any qualms with it personally, but as someone who only deals in C++, I'm not familiar with what issues this could bring about for binding maintainers.

@sonoro1234 and @ocornut, your insight here would be greatly appreciated!

@epezent
Copy link
Owner

epezent commented Sep 2, 2020

@hoffstadt, can you comment as well and let us know how this would impact Dear PyGui

@hoffstadt
Copy link

hoffstadt commented Sep 2, 2020

Thanks for mention. This would not impact Dear PyGui. Although we are not restricted to using floats, we've done so out of consistency with Dear ImGui. Ideally we would like to begin using doubles and ints to avoid all the casting taking place from:

Python -> Python C API -> ImGui/ImPlot.

I personally think templates in this case would be a good idea and I like the the approach taken in your comment above.

Omar may have a more insightful view on potential issues for his users.

@sonoro1234
Copy link
Contributor

I can convert a LuaJIT function to typedef ImPlotPoint*(*getter)(void* data,int idx) ; but not to typedef ImPlotPoint(*getter)(void* data,int idx) ;

It happens with LuaJIT but I think it is a general C ffi issue: a C function can return a UDT* type but cant return a UDT type

@epezent
Copy link
Owner

epezent commented Sep 3, 2020

@sonoro1234 More specifically I was wondering how having a template based plotting API would impact cimplot (see my last comment with code).

@sonoro1234
Copy link
Contributor

The generic ImGuiDataType is completely C friendly while the templated function would need some work on my side. (now cimgui takes care of templated structs still not templated functions)

@epezent
Copy link
Owner

epezent commented Sep 3, 2020

May I ask how much work and would you be willing to implement it? The templated approach would be ideal for most users on our side.

@sonoro1234
Copy link
Contributor

sonoro1234 commented Sep 4, 2020

If it means generating

CIMGUI_API void ImPlot_PlotLineT_ImS8(const char* label_id, const ImS8* xs, const ImS8* ys, int count, int offset, int stride)
{
    return ImPlot::PlotLineT<ImS8>(label_id,  xs,ys,  count, offset, stride);
}

and similars, it seems quite doable.

But explicit instantiation

template void PlotLineT<ImS8>(const char* label_id, const ImS8* xs, const ImS8* ys, int count, int offset, int stride);

goes in .cpp file

Could we have something related in header file? (I am only using header files for parsing)

That should be the ideal but if it is not possible then we will need to provide the typenames desired as parameters for the generator

@epezent
Copy link
Owner

epezent commented Sep 6, 2020

To avoid cluttering the header, would a persistent comma-separated comment suffice?

// Supported Data Types: double, float, ImS32, ImU32, ...

Might also be possible with a macro in imconfig.h?

Ideally every plotting function would adopt this templated approach, so you could assume that each one will support all the data types listed.

@epezent
Copy link
Owner

epezent commented Sep 7, 2020

@electromaggot , I've converted about half the plotting API to the proposed template approach. You can check it out in the templates branch. Can you try plotting your Int16 data with these are let me know how it goes?

https://github.com/epezent/implot/tree/templates

@sonoro1234 , do you want to give it a look as well and let me know what you think?

As expected, the compile size increased. Depending on the compiler, I see about a 1.5x to 3x increase in the lib file size if ImPlot is compiled separately (tested MSVC, GCC, and Clang). It's still only a few MB though. As far as the executable size goes, you only pay for what you use, so that remains unchanged. So I'd say it's a non-issue.

I'm not certain what issues this would have if ImPlot were linked as a shared library. ImGui recommends not doing this, and so I don't think it's a real concern. Most users will add the source files directly to their own anyway.

However, I'm still undecided if this is a good design or a bad design...

@sonoro1234
Copy link
Contributor

sonoro1234 commented Sep 7, 2020

@sonoro1234 , do you want to give it a look as well and let me know what you think?

This are the generations for PlotLine (They dont seem to need <ImS8>)

https://github.com/cimgui/cimplot/tree/templates

CIMGUI_API void ImPlot_PlotLinedoublePtrInt(const char* label_id,const double* values,int count,int offset,int stride)
{
    return ImPlot::PlotLine(label_id,values,count,offset,stride);
}
CIMGUI_API void ImPlot_PlotLineFloatPtrInt(const char* label_id,const float* values,int count,int offset,int stride)
{
    return ImPlot::PlotLine(label_id,values,count,offset,stride);
}
CIMGUI_API void ImPlot_PlotLineS32PtrInt(const char* label_id,const ImS32* values,int count,int offset,int stride)
{
    return ImPlot::PlotLine(label_id,values,count,offset,stride);
}
CIMGUI_API void ImPlot_PlotLineU32PtrInt(const char* label_id,const ImU32* values,int count,int offset,int stride)
{
    return ImPlot::PlotLine(label_id,values,count,offset,stride);
}
CIMGUI_API void ImPlot_PlotLinedoublePtrdoublePtr(const char* label_id,const double* xs,const double* ys,int count,int offset,int stride)
{
    return ImPlot::PlotLine(label_id,xs,ys,count,offset,stride);
}
CIMGUI_API void ImPlot_PlotLineFloatPtrFloatPtr(const char* label_id,const float* xs,const float* ys,int count,int offset,int stride)
{
    return ImPlot::PlotLine(label_id,xs,ys,count,offset,stride);
}
CIMGUI_API void ImPlot_PlotLineS32PtrS32Ptr(const char* label_id,const ImS32* xs,const ImS32* ys,int count,int offset,int stride)
{
    return ImPlot::PlotLine(label_id,xs,ys,count,offset,stride);
}
CIMGUI_API void ImPlot_PlotLineU32PtrU32Ptr(const char* label_id,const ImU32* xs,const ImU32* ys,int count,int offset,int stride)
{
    return ImPlot::PlotLine(label_id,xs,ys,count,offset,stride);
}

@ocornut
Copy link
Collaborator

ocornut commented Sep 7, 2020

As expected, the compile size increased. Depending on the compiler, I see about a 1.5x to 3x increase in the lib file size if ImPlot is compiled separately (tested MSVC, GCC, and Clang). It's still only a few MB though. As far as the executable size goes, you only pay for what you use, so that remains unchanged. So I'd say it's a non-issue.

Your suggestion here:
#104 (comment)
Suggest only minor stub functions are instanciated (and likely <1 KB), not the full plotting code? At which point did it turns into "a few extra megabytes"?

I agree people using non-C/C++ backends are likely to not mind the size increase, but C/C++ crowd may, in particular people using dear imgui on the web with emscripten, it also tends to have an effect on compilation and link time, if it can be avoided it would be a nice bonus doing so.

@sonoro1234
Copy link
Contributor

sonoro1234 commented Sep 7, 2020

To avoid cluttering the header, would a persistent comma-separated comment suffice?

This needs cimplot generator to be manually customized adding

--this list will expand all templated functions
parser.ftemplate_list = {"float", "double", "ImS8", "ImU8", "ImS16", "ImU16", "ImS32", "ImU32", "ImS64", "ImU64"}

Getting this info from the header in any standarized way would be a completely automatic solution. But I am not being able to find such a standarized way!! (it seems that instantiations must live in cpp files)

Also would avoid all templated functions expanded by the same ftemplate_list. But this could be solved by each templated type having its own list:

--this list will expand all templated functions
parser.ftemplate_list = {}
parser.ftemplate_list.T = {"float", "double", "ImS8", "ImU8", "ImS16", "ImU16", "ImS32", "ImU32", "ImS64", "ImU64"}
parser.ftemplate_list.T2 = {"float", "double"}

@epezent
Copy link
Owner

epezent commented Sep 7, 2020

@sonoro1234 :

This are the generations for PlotLine (They dont seem to need )

Yes, I should have mentioned that API functions can be called without the explicit template parameter, which is a nice bonus.

(it seems that instantiations must live in cpp files)

Correct, there's no way to put them in the header without exposing the rest of the template code.

Also would avoid all templated functions expanded by the same ftemplate_list.

I think this may be ok, since I plan to make each plotting function accept all 10 types. However, we could add a comment for each function with the listed supported types.

@ocornut :

Suggest only minor stub functions are instanciated (and likely <1 KB), not the full plotting code? At which point did it turns into "a few extra megabytes"?

Actually, it's nasty template code all the way down. In my initial testing, it seemed that we gained an appreciable FPS boost from a template based design. Unfortunately that means the compiler is generating a bunch of functions/objects like this:

RenderPrimitives<ShadedRenderer<GetterXsYs<ImS32>,GetterXsYRef<ImS32,double>,TransformerLinLin>>(...) 

Perhaps I went a little overboard, but as I said, it seemed to pay off (see #41 (comment), which compares basic ImGui AddLine (row 1 and 2) with a few template based approaches I was testing.)

Here are some numbers for compile size and time. Each compiler is set to Release mode with CMake. Beyond that I don't know how each is optimizing the build.

Compiler implot.lib (master) implot.lib (templates) implot_demo.exe (master) implot_demo.exe (templates)
MSVC 1,921 KB (2.5s) 3,996 KB (3.729) 990 KB 996 KB
Clang 1,013 KB (3.6s) 2,045 KB (9.1s) 1,056 KB 1,060 KB
GCC 546 KB (5.0s) 1,129 KB (12.9s) 1,757 KB 2,230 KB

Regarding compile time: because the template instantiations are in the cpp file, this should be a one-time cost to the user.

@epezent
Copy link
Owner

epezent commented Sep 7, 2020

Not to derail the topic here, but @ocornut you may find this interesting. Here's a comparison a few different rendering methods using our new benchmark tool #102 :

Run 1 - ImPlot's custom template based rendering pipeline (no AA is done)
Run 2 - Calling ImGui DrawList.AddLine per segment w/ ImGuiStyle.AntiAliasedLines enabled
Run 3 - Calling ImGui DrawList.AddLine per segment w/ ImGuiStyle.AntiAliasedLines disabled

image

@ocornut
Copy link
Collaborator

ocornut commented Sep 7, 2020

Actually, it's nasty template code all the way down.

It I look at your first message in the thread it seems like the only template variations required are due to the fact that the getter return a strong type, if the getter wrote to an opaque pointer wouldn't it fix it?

Each compiler is set to Release mode

Out of curiosity I would suggest also comparing unoptimized builds. If you optimize for Release you only optimize for the best-case scenario, whereas the real-world is also interested in the daily-scenario which may not be full-optimization on.

Not to derail the topic here, but @ocornut you may find this interesting. Here's a comparison a few different rendering methods using our new benchmark tool #102 :

That's great! I hope your automation framework in imgui_dev could benefit from some of those plotting later.

Do you know where are the gains from? if there is something we could report to ->AddLine?

@epezent
Copy link
Owner

epezent commented Sep 7, 2020

I've completed templetizing the library on the templates branch. The final compile sizes aren't significantly larger than what I posted above, and compile times are totally reasonable. A couple notes:

  1. The supported scalar types are documented in implot.h. Again, they are:
    float, double, ImS8, ImU8, ImS16, ImU16, ImS32, ImU32, ImS64, ImU64
  2. Since those types are just typedefs, it's still possible to use built-in types like int, short, size_t, etc.
  3. Plotting functions which accept a custom getter function pointer posed an issue. The compiler tried to instantiate the template functions with the function pointer type. And so I had to introduce a G post-fix to these functions, e.g. PlotLineG. I may choose another post-fix letter, but there's no way around it as far as I can tell.
  4. Previously some plotting functions accepted either ImPlotPoint* or ImVec2*. While not documented or obvious, passing these still works as they are specialized in implot_items.cpp. However, striding should be preferred for plotting struct data, and it's unlikely that people will actually use either of these types over their own.
  5. On that note, I added some documentation to the header on custom getters and striding.
  6. I sprinkled different types throughout the demo as a sanity check and as an example. Everything works as expected, though we might want to do some more thorough tests for all possible combinations down the road.

Unless there are any major objections, I'm going to merge this later tonight after a few more checks with different compilers and also trying to export the functions in a shared library. Overall this is a big step in the right direction to making ImPlot usable for everyone!

@ocornut, I'll respond to your questions in an issue on the ImGui repo.

@epezent
Copy link
Owner

epezent commented Sep 8, 2020

This work is completed and merged to master.

I did a few final checks, verifying multiple compilers and that ImPlot can be compiled and used as DLL with the template API. I half expected there to be export issues, but surprisingly there were none (save the typical MSVC __declspec nonsense, which is solved with a good ole IMPLOT_API macro).

@sonoro1234 , feel free to open a separate issue where we can discuss what modifications are necessary for cimplot. @hoffstadt , let me know if you have any issues with Python.

@epezent epezent closed this as completed Sep 8, 2020
@hoffstadt
Copy link

@epezent Thanks for all the great work man (If I see you in Houston, I'll buy you a beer). I plan on finishing the wrapping within a week or so!

@electromaggot
Copy link
Author

electromaggot commented Sep 9, 2020

Greetings again Evan, this is all such excellent work - you are prolific!
...and sorry for my late follow-up. Over the last week I'd try things and accumulate comments that became irrelevant as your work (and this discussion) progressed!

I did implement ImPlotPoint getters as you originally suggested, resulting in some beautiful audio-data graphs. Then I fell down a rabbit hole trying to profile/trace through assembly, seeing how much of the function call overhead the compiler might optimize away (turns out not much, at least for clang).

Anyway from my "user perspective" I'm grateful for your continued valiant effort. Your design seems sound, your code tidy considering the increased flexibility, and your consideration of issues like compile times (developer's daily achilles heel) versus lib/executable sizes (and templates' "you only pay for what you use" (my exact words in a deleted comment) although lib-wise handled differently per build system) all extremely important!

Yesterday I tried to convert my ImPlot::PlotLineG versions to something like e.g. ImPlot::PlotLine<ImS16>("##amplitude", (ImS16*) audio.pBuffer, count, offset, stride). Stride being important to take slices of the huge quantity of data. Count and offset important because my buffer is circular, so I potentially have to make two calls to PlotLine. I think.

I'm struggling with this a bit, because I guess I should use the version of ImPlot::PlotLine that also includes the Xs Getter as well... however, creating an entire ascending array for one discontinuity seems wasteful. Do you have any suggestions on how to deal with that? Otherwise I'll keep banging on it!

image

By the way here's a pic of the particular graph I'm attempting. My old ImPlot::PlotLineG version on the bottom -- it's somewhat like SoundCloud, showing sample amplitude at a given timeslice, which here I also have scrolling in realtime seconds on the X-axis. The upper plot is my new ImPlot::PlotLine<ImS16> attempt, or at least one part of its data that's circularly buffered (and no scrolling axis or reworked axes labels... but one step at a time!).

Thanks again for everything, Evan!

@epezent
Copy link
Owner

epezent commented Sep 9, 2020

If I understand your problem, you want to use the single array version of PlotLine (i.e. values* version), but you want seconds on the X-axis instead of the index. And you don't want to have to generate an X array for each plot that would be required for the xs*, ys* version of PlotLines. Correct?

We could potentially add xscale and xoffset parameters to the values* version. This would let you choose the spacing and start value of X.

I was also thinking you could generate a static array of equally spaced times (e.g at 48 kHz), say up to 10 minutes, and use it for every one of your audio plots (assuming they all have the same sample rate). However, I see now the issue is that with the current template design you can't have floating point Xs if your Ys are int16 because both arrays are templated on type T. I was wondering how long it would be before someone wanted to plot non-homogenous data. The only solution to this I'm afraid is taking a properly templated approach.

Out of curiosity, do you see a performance gain between the function pointer version PlotLineG and PlotLine<ImS16>?

@epezent
Copy link
Owner

epezent commented Sep 9, 2020

I'll think on the possibility of exposing a stripped down header file with fully templated plotting functions. It should be possible, I just have to figure out the best way to decouple it fromimplot_items.cpp.

@epezent epezent reopened this Sep 9, 2020
@electromaggot
Copy link
Author

Thanks for your thoughts Evan and especially for troubling to consider my noobish requests!

First of all, PlotLine<ImS16> seems incredibly efficient! Congrats on a huge performance gain over PlotLineG which, while generally not hitting my FPS "too" terribly, would become increasingly noticeable with a large int count parameter, especially on one of my graphs tying it to window width, potentially stretched wide on a big monitor. I guess that's obvious, but PlotLineG is still very important for its sheer flexibility, while your new scalar versions add to your arsenal on the performance side, like for realtime graphs. PlotLine<ImS16>'s impact on my FPS seemed darn-near painless, even with a very wide window! Although I still have yet to get my version of it working quite right.

If I understand your problem, you want to use the single array version of PlotLine (i.e. values* version), but you want seconds on the X-axis instead of the index.

Correct, but may I add "in appearance" to your last clause. X-axis can be an index, say in terms of granularity, but what would be really nice is if there was some kind of conversion I could apply to that index before it displays on axis or even hover. In a way, this seems like something that PlotLineG already allows me to do: take the int X index and return a float. Floating point format suffices for me to display elapsed seconds.

However, on my "wish list," if I may be so bold, would be for this conversion to somehow be flexible enough to display a format like MM:SS.s or heck HH:MM:SS.s, which would be programmatic. I know your custom ticks allow for something like this, which I was hoping to be my saving-grace to override the X-axis indices.

And you don't want to have to generate an X array for each plot that would be required for the xs*, ys* version of PlotLines. Correct?
We could potentially add xscale and xoffset parameters to the values* version. This would let you choose the spacing and start value of X.

Line#1: Yes, exactly! Line#2: This would absolutely solve my problem, which is that I'm having to end my plot prematurely due to the discontinuity in my circular buffer. xoffset would allow me to resume/finish the plot.

Before you suggested this, I was even thinking I could call PlotLine<ImS16>(const char* label_id, const T* values, ...) twice with the same label_id (maybe like how Dear ImGui signifies labels) and that it would "resume" drawing at its last X!

However, I see now the issue is that with the current template design you can't have floating point Xs if your Ys are int16 because both arrays are templated on type T. I was wondering how long it would be before someone wanted to plot non-homogenous data. The only solution to this I'm afraid is taking a properly templated approach.

While I think your static array suggestion is intriguing (and doable), what you're saying here about non-homogeneous data, in my opinion, isn't necessary. It overcomplicates, when "simplicity is beautiful." I say your current approach is perfectly ideal, not "mixing worlds" between ints and floats when most devs want to handle those worlds on fully separate terms at their appropriate times.

The compromise, I think, is in allowing axes units/formats, for instance, to be re-labelled... if someone wants that mix for display purposes. So I think your current templates are more than adequate! Great work Evan.

@epezent
Copy link
Owner

epezent commented Sep 10, 2020

However, on my "wish list," if I may be so bold, would be for this conversion to somehow be flexible enough to display a format like MM:SS.s or heck HH:MM:SS.s?

Are you aware of the new time formatted axes? #48 (comment) It's not exactly what you'd want in this case (because it uses a UNIX timestamp and has a date associated with the time), but it might be useful.

Before you suggested this, I was even thinking I could call PlotLine(const char* label_id, const T* values, ...) twice with the same label_id (maybe like how Dear ImGui signifies labels) and that it would "resume" drawing at its last X!

It won't resume at the last index, but the two lines will be grouped together in the Legend.

The compromise, I think, is in allowing axes units/formats, for instance, to be re-labelled... if someone wants that mix for display purposes. So I think your current templates are more than adequate! Great work Evan.

I agree. We can add the scaling and offset to these functions. Regarding support for custom labels programatically, I don't have an immediate answer for you. A formatting callback comes to mind, but that would seem to violate the ImGui philosophy. What would be the ideal interface for this, in your opinion?

@electromaggot
Copy link
Author

It won't resume at the last index, but the two lines will be grouped together in the Legend.

This makes complete sense and seems, as I see now, consistent with ImGui.

Are you aware of the new time formatted axes?
...
What would be the ideal interface for this, in your opinion?

Re: time formatted axes: great feature! Although in my case, all I really need is what you have already provided: SetNextPlotTicksX ...which I can indeed generate programmatically!

Using SetNextPlotTicksY worked perfectly to map my -32768 to 32767 Y-axis to a "non type exposing" -1.0 to 1.0 range.

I'm thinking that with your addition of xoffset and my ability to update X labels[] strings on-the-fly, I can get my PlotLine<ImS16> graph scrolling again, and with my arbitrarily-customized time format. One complicating factor I foresee: increasing the number of labels as the window gets stretched wider; I'll experiment with that.
(I might have to miss work today! ;-)

Again, Evan, thanks! This is all a lot of fun.

@epezent
Copy link
Owner

epezent commented Sep 10, 2020

@electromaggot , check the latest version of master! :)

template <typename T> IMPLOT_API void PlotLine(const char* label_id, const T* values, int count, double xscale=1, double x0=0, int offset=0, int stride=sizeof(T));

xscale

@electromaggot
Copy link
Author

electromaggot commented Sep 11, 2020

Ha ha INCREDIBLE!

Now you check this out! (sorry for crappy capture quality and non-realtime speed)

SoundCloudish

You rock, Evan! I owe you a zillion thanks... and a second beer should I make it to Houston. I will keep looking at your code and hopefully at some point I can contribute to this awesome project. Meantime, keep rockin' and thanks again!

(edit: By the way, the top graph is real unadulterated Spotify SP_SAMPLETYPE_INT16_NATIVE_ENDIAN data. I hit "Play" in my app, that data starts streaming and fills a 4-second buffer as it plots to the right. The audio device consumes the data on the left, which plays out the speakers right about the time the plot hits the left edge. It's easy see how fast the stream buffers-ahead and outpaces the playback. This buffer is circular, so when it wraps, I do two PlotLine calls leveraging your new x0 parameter. Thanks to your efforts, it was so easy for me!)

@ocornut
Copy link
Collaborator

ocornut commented Sep 11, 2020

Your work is amazing Evan! Thanks for doing all this :)

@epezent
Copy link
Owner

epezent commented Sep 11, 2020

@electromaggot, that's awesome man! I'm feeling inspired to experiment with audio plotting now. Happy to hear that the new functions are working for you.

@ocornut, thank you! That means a lot. I'm just trying to maintain the bar you've already set with ImGui! (PS: still going to post an issue on ImGui regarding line optimizations. Just busy).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants