How to add minor division lines to a chart?

Important: unclear posts may not receive useful answers.

Before posting

  • Get familiar with Markdown to format and structure your post
  • Be sure to update lvgl from the latest version from the master branch.
  • Be sure you have checked the relevant part of the documentation.
  • If applicable use the Simulator to eliminate hardware related issues.

Delete this section if you read and applied the mentioned points.

Description

I have a chart that looks like this

image

I want to add it minor division lines that will provide additional resolution in reading the values. Here is an example one I mean but of course I will pick a color and line thickness that will make the minor division lines less visible than the primary divisions.

image

What MCU/Processor/Board and compiler are you using?

STM32411CE black pill.

What LVGL version are you using?

I can upgrade to the latest if needed.

What do you want to achieve?

Adding horizontal and vertical minor division lines that are less visible than the major division lines which are on axis tick values.

What have you tried so far?

Reading the LVGL Chart documentation

Code to reproduce

Add a code snippet which can run in the simulator. It should contain only the relevant code that compiles without errors when separated from your main code base.

The code block(s) should be formatted like:

/*You code here*/

Screenshot and/or video

If possible, add screenshots and/or videos about the current state.

Unfortunately, the chart doesn’t support it. You can either tweak the drawing routing of the chart or the lines manually using the lv_line widget.

Thanks @kisvegabor. Are there some drawing hooks that I can use to modify the rendering or do I actually need to modify the chart’s source code? (I let platformio to manage the LVGL library so local code modifications will break it).

As for for lv_line widget, can I make it displayed below the data graphs and major divisions and above the chart background? (Is it possible to tell the chart not to draw the background without incurring a significant rendering time due to the use of non opaque colors?).

In v8 you can hook drawing. I’ve just updated the chart to add the hooking option for the division line too.
It look slike this:
image

Example:

Thanks @kisvegabor. I think this is what I am looking for. Will spend time trying to understand the example.

If I got it correctly, the hook doesn’t add division lines but just modify the look of some lines based on their ids. In other words, without the hook to the total number of vertical and horizontal lines will be the same and they will all be dash/broken lines. Is it so?

Yes, that’s the case.

My upgrade to V8 is going slow so I decided to patch LVGL V7 to support minor division lines until I will complete the upgrade to V8. I took a different approach of using the minor division lines spec from the main background part and controlling the selection of the minor div lines using a user provided bit mask.

It looks good but has a minor artifact at the top of the chart because of the vertical minor division lines intersect with the top line of the frame and are drawn after it. I believe this will also happen with the callback approach of V8, unless it will also have some prioritization of the drawing of the div lines.

image

The artifact can be fixed if major line width is added to the start point of the vertical lines.

In v8 you can change the points that LVGL will use to draw the lines. So you can control the overlapping in a flexible way.

That’s a good point. Will this also solve the crossings, inside the chart area, of major and minor divisions, where the minor is drawn after the major?

Rendering all the minors before all the majors may give the cleanest outcome.

True, it won’t solve it.

In v8 you could save all the major positions, make LVGL not render them, and on the last minor draw the majors. But - as LVGL is open source - you can add custom behaviour in the code directly too. :slightly_smiling_face:

That what I just did :wink: But in a sense it becomes a fork and requires a three way merge when moving to a newer version.

and on the last minor draw the majors.

That’s a good point, does the LVGL callback indicates the end of the grid rendering or does the user need to figure it on their own?

I made some tests and haven’t found straightforward points where the skipped lines can be drawn. LVGL send part draw events before and after

  1. the main part (the background rectangle) is drawn
  2. every divisor lines

Theoretically, the best would be to send an event after the last divisor line but before drawing the series, right?

Yes, I think so. BTW, with this approach, will the user also be able to define zero division lines and once getting this callback render all the divisions manually?

Another approach would be to let the callback also specify the priority of the line (e.g. 0 to 3) such that in the first path LVGL draws only priority 0 and buffers the rest and then draws them in consecutive passes automatically. This way users don’t have to add logic to manage their buffered lines.

Interesting idea but I think an event before and after drawing all the div lines is more powerful. E.g. in some cases, you might want to draw skew, circular or radial division lines.

I’m thinking about sending the same event as for division line but instead of

 obj_draw_dsc.line_dsc = &line_dsc;
 obj_draw_dsc.p1 = &p1;
 obj_draw_dsc.p2 = &p2;
 obj_draw_dsc.id = i;

set

 obj_draw_dsc.line_dsc = &line_dsc;   //Keep it to show that the event is related to lines
 obj_draw_dsc.p1 = NULL;
 obj_draw_dsc.p2 = NULL;
 obj_draw_dsc.id = 0xFFFFFFFF;

to indicate these are not related to any specific line.

What do you think?

My understanding is that you want to encode two or more events types in the same callback signature. If so, how about a straight forward C encoding as an enum for the type and a matching union for the values? It will allow you to add more event types in the future in a straight forward way. Are there performance concerns?

BTW, you will also need to specify if each callback gets a copy of the original line_dsc (intuitive?) or the left over from previous call (confusing?).

Using an enum seems complicated because the widget types are independent but the enum should be shared. E.g. there is an event for the main part. lv_obj sends an event when it draws the background rectangle. The div lines f the chart also uses the line style fror the main part so they also send an event when the chart draws the div line. An enum could look like:

enum {
   DRAW_MAIN_RECTANGLE
   DRAW_CHART_DIV_LINES
   ...
}

But where this enum should be? If it’s in lv_chart.h than lv_obj can’t use it becasue it shouldn’t see the chart’s API.

Or something like:

//lv_obj.h
enum {
   DRAW_MAIN_RECTANGLE,
   DRAW_SCROLLBAR,
  _DRAW_OBJ_LAST,
};

//lv_chart.h
enum {
   DRAW_CHART_DIV_LINES = _DRAW_OBJ_LAST,
   DRAW_CHART_LINE
   DRAW_CHART_POINT
   DRAW_CHART_BAR
   ...
};

It seems in case of the chart I missed it, but normally a copy of the original draw dsc should be used.

@kisvegabor, if having a enum will be confusing, not having it will be even more confusing since the enum doesn’t introduce the multiple cases of calling the callback but clarifies them.

Isn’t this enum similar to the PARTs enum where some values are used at the obj level and some at the chart and series levels? In that case, can’t this enum be defined near the PARTs enum?

Edit: BTW, having the enum will make it much easier for you to describe the various cases in the documentation.

Parts are independent of the widget type but with this enum we want to express the deepest widget-specific stuff.
Anyway, I agree that the current method is vague in some places.

I’m thinking about letting the widget register there values. E.g.
DRAW_ARC_BG = lv_register_draw_part() but

  • It’s complicated for the Micropython binding
  • It’s not clear when to call these registrations s the widget types doesn’t have init functions

So in summary:

  • The current approach is vauge
  • The enums are hard to extend by inherited widget types
  • The dynamic registration is problematic for the above-mentioned reasons.

Let’s sleep on it, hopefully, we will get a good idea :slight_smile:

One more summary point :wink:

  • It would be trivial with C++ (and possibly will also map well to Python and other OO languages)

class obj_event_handler {
void on_event_a(…);
void on_event_b(…);
}

class chart_event_handler : obj_event_handler {
void on_event_c(…);
void on_event_d(…);
}

1 Like

Sleeping always helps. Here is an idea :smiley:

If we stored the class of the sender object in lv_obj_draw_part_dsc_t we could use widget specific enum.

Examples for LV_EVENT_DRAW_PART_BEGIN events:

  1. When the chart’s background is drawn. dsc->class_p == &lv_obj_class shows that now lv_obj draws and dsc->enum (stupid name, just for the example) can be DRAW_OBJ_...
  lv_obj_draw_part_dsc_t * dsc = lv_event_get_draw_part_dsc(e);
  if(dsc->class_p == &lv_obj_class && dsc->enum == DRAW_OBJ_BG_RECTANGLE) {
  
  }
  1. When the parts of the chart are drawn:
  lv_obj_draw_part_dsc_t * dsc = lv_event_get_draw_part_dsc(e);
  if(dsc->class_p == &lv_chart_class) {
    switch(dsc->enum) {
       case DRAW_CHART_DIV_LINES:
       break; 

       case DRAW_CHART_SERIES_LINE:
       break; 

       case DRAW_CHART_SERIES_POINT:
       break; 

       case DRAW_CHART_SERIES_BAR:
       break; 
   }  
}

It’s a bonus that it can be added without API breaking.

What do you think?