Search code examples
javascripthtmlrechartsecharts4r

Echarts4r - how to right align values in a custom tooltip?


I want to reproduce the default tooltip with some additions. However, when I try to do so, it left-aligns both serie and group names, and I am not sure how to fix it.

Some sample data and what I tried to do:

library(dplyr)
library(echarts4r)
library(htmlwidgets)

set.seed(10)
data <- data.frame(name = rep(c("Bob", "Michael"), each = 10), 
                   x = rep(0:9, 2), 
                   y = sample(0:5, 20, replace = TRUE),
                   add = sample(1:100, 20, replace = TRUE))

# Default tooltip
data %>% 
  group_by(name) %>% 
  e_chart(x = x) %>% 
  e_line(serie = y) %>% 
  e_tooltip(trigger = "axis")

# My attempt to recreate the formatting while adding new things
data %>% 
  group_by(name) %>% 
  e_chart(x = x) %>% 
  e_line(serie = y, bind = add) %>% 
  e_tooltip(trigger = "axis",
            formatter = JS("
              function(params){
              var colorSpan = color => `<span style='display:inline-block;margin-right:5px;border-radius:10px;width:9px;height:9px;background-color:` + color + `'></span>`;
              let rez = '<strong>Day ' + params[0].value[0] + '</strong>';
              
              params.forEach(item => {
                                       var xx  = '<br />' + colorSpan(item.color) + ' ' + item.seriesName + 
                                                 ' <strong>' + item.value[1] + '</strong> (' + item.name + ')'
                                       rez += xx;
                                     });
  
              return (rez)}"))

So, the default tooltip looks like this (values are right aligned):

values are right aligned

And my tooltip looks something like this, which is not very readable:

values are left aligned

I want to add things to the tooltip while keeping the formatting mostly untouched, but do not know how to do so with right alignment. I am not very familiar with echarts and JS in general though, so I have some troubles with it.

EDIT

So, thanks to @Russ we now have a workaround, which is not exactly what I was hoping to get, but a solution nonetheless. Does not look as pretty as the default tooltip, but for now we have what we have. Still, @Russ's solution does have some issues, so I post my edited version of his answer here. Not sure how to apply css to echarts' tooltip to remove margins caused by <pre> tag, but that does not matter too much right now

# Adding whitespaces after the name
data <- data %>%
  mutate(nameAlt = stringr::str_pad(name, max(nchar(name)), "right"))

data %>% 
  group_by(nameAlt) %>% 
  e_chart(x = x) %>% 
  e_line(serie = y, bind = add) %>% 
  e_tooltip(trigger = "axis",
            formatter = JS("
              function(params){
              var colorSpan = color => `<span style='display:inline-block;margin-right:5px;border-radius:10px;width:9px;height:9px;background-color:` + color + `'></span>`;
              let rez = '<strong>Day ' + params[0].value[0] + '</strong><pre>';
              
              params.forEach(item => {
                                       var xx  = colorSpan(item.color)  + item.seriesName + 
                                                 '<span style=`float:right;margin-left:20px;`><strong>' + item.value[1] + '</strong> (' + item.name + ')</span></br>'
                                       rez += xx;
                                     });
              rez += '</pre>'
  
              return (rez)}")) %>% 
  # Removing whitespaces from the legend
  e_legend(formatter = JS("function(name){return(name.trim())}"))

Result:

enter image description here


Solution

  • Overview

    The way to solve this is with a combination of html tags with css styles that you can specify inside of the javascript. I tried playing around with it and came up a solution that does what you want at the cost of changing the font and probably not being best practice html/css. So my solution isn't a great one, but it does fix the spacing and keep most of your original formatting.

    What I did was use R to add white space to the end of Bob's name so that his name has the same number of characters as Michael's name. So like "Bob" becomes "Bob ". I wrote the code so that it finds the longest name in the data and then adds a variable amount of white space to all the other names so that they're all the same length. It should work with an arbitrary number of names.

    Then I added a <pre></pre> html tag around the rows in the tool tip. I put <span></span> tags around the numbers in each row and then defined a style that included a bit of a left side margin to separate Bob and Michael from the numbers.

    Code

    Add white space to names in R

    # Find the longest name in the data
    longest_name_length <- data$name %>%       # names column
                           unique()  %>%     # unique values in the name column
                           nchar() %>%       # number of characters in each unique name
                           max(na.rm = TRUE)  # longest name 
    
    
    # add a new column with the length of the name
    data$name_length <- data$name %>%     
                        nchar()
    
    # add a characters to add column
    data$characters_to_add <- longest_name_length - data$name_length
    
    
    # add white space to the shorter characters so that all names are the same length
    
    # add a variable amount of white space to each name using a loop
    for (row in 1:nrow(data)){
      
     # Rep " " by the number of characters to add and then collapse 
    # that vector into a single string using paste 
    white_spaces <- rep(" ", times=data$characters_to_add[row]) %>%
                       paste(collapse = "") 
                       
    
    # paste the name to the white spaces string
    data$name[row] <- paste0(data$name[row], white_spaces)
    
    }
    

    update html tags in the code for the chart

    data %>% 
      group_by(name) %>% 
      e_chart(x = x) %>% 
      e_line(serie = y, bind = add) %>% 
      e_tooltip(trigger = "axis",
                formatter = JS("
                  function(params){
                  var colorSpan = color => `<span style='display:inline-block;margin-right:5px;border-radius:10px;width:9px;height:9px;background-color:` + color + `'></span>`;
                  let rez = '<strong>Day ' + params[0].value[0] + '</strong>';
                  
                  params.forEach(item => {
                                           var xx  = '<pre>' + colorSpan(item.color)  + item.seriesName + 
                                                     '<span style=`float:right;margin-left:15px;`> <strong>' + item.value[1] + '</strong> (' + item.name + ')</span></pre>'
                                           rez += xx;
                                         });
      
                  return (rez)}"))
    

    In this part var xx = '<br />' + I replaced a </br> tag with a <pre> and then in this part
    '</strong> (' + item.name + ')' I added ended a </pre> to close the tag. Around this part
    <strong>' + item.value[1] + '</strong> (' + item.name + ') I added tags to group these items into an inline html element and then inside I specified a left margin using css to add space between Bob/Michael and their numbers with this style=`float:right;margin-left:15px;`

    Result

    enter image description here

    Other thoughts

    This is a javascript question, but it's also an html/css question. It might be worth it to add those tags and hope for a less hacky solution than mine :D