Flip/reverse colour legend when formatting table

I want to colour a column by value with colour scale according to value high (in Red colour) to small (in Green colour).

However, by using default ‘RdYlGn’, I am only be able to do it in the opposite way, such as .

def discrete_background_color_bins(df, n_bins=7, columns='all'):

 bounds = [i * (1.0 / n_bins) for i in range(n_bins+1)]
 if columns == 'all':
     if 'id' in df:
         df_numeric_columns = df.select_dtypes('number').drop(['id'], axis=1)
     else:
         df_numeric_columns = df.select_dtypes('number')
 else:
     df_numeric_columns = df[columns]
 df_max = df_numeric_columns.max().max()
 df_min = df_numeric_columns.min().min()
 ranges = [
     ((df_max - df_min) * i) + df_min
     for i in bounds
 ]
 styles = []
 legend = []
 for i in range(1, len(bounds)):
     min_bound = ranges[i - 1]
     max_bound = ranges[i]
     backgroundColor = colorlover.scales[str(n_bins+4)]['div']['RdYlGn'][2:-2][i - 1]
     color = 'black'

     for column in df_numeric_columns:
         styles.append({
             'if': {
                 'filter_query': (
                     '{{{column}}} >= {min_bound}' +
                     (' && {{{column}}} < {max_bound}' if (i < len(bounds) - 1) else '')
                 ).format(column=column, min_bound=min_bound, max_bound=max_bound),
                 'column_id': column
             },
             'backgroundColor': backgroundColor,
             'color': color
         })
     legend.append(
         html.Div(style={'display': 'inline-block', 'width': '60px'}, children=[
             html.Div(
                 style={
                     'backgroundColor': backgroundColor,
                     'borderLeft': '1px rgb(50, 50, 50) solid',
                     'height': '10px'
                 }
             ),
             html.Small(round(min_bound, 2), style={'paddingLeft': '2px'})
         ])
     )

 return (styles, html.Div(legend, style={'padding': '5px 0 5px 0'}))

Can some one let me know how to highlight high values in Red and Small value in Green (or Blue) ?

Thanks.

Hi @TristanSun

You can try reversing the order of the backgroundColors before the loop starts. Here is a MWE based on the example in the conditional formating docs. In the code below just add or remove the line: backgroundColors.reverse()

image
image


import dash
import dash_table
import pandas as pd
import dash_html_components as html
import colorlover


data = dict(
    [
        ("Firm", ["Acme", "Olive", "Barnwood", "Henrietta"]),
        ("2017", [10, 13, 3, 13]),
        ("2018", [5, 3, 7, 1]),
        ("2019", [10, 13, 3, 13]),
        ("2020", [4, 3, 6, 1]),
    ]
)
df = pd.DataFrame(data)

app = dash.Dash(__name__)


def discrete_background_color_bins(df, n_bins=7, columns="all"):
    bounds = [i * (1.0 / n_bins) for i in range(n_bins + 1)]
    if columns == "all":
        if "id" in df:
            df_numeric_columns = df.select_dtypes("number").drop(["id"], axis=1)
        else:
            df_numeric_columns = df.select_dtypes("number")
    else:
        df_numeric_columns = df[columns]
    df_max = df_numeric_columns.max().max()
    df_min = df_numeric_columns.min().min()
    ranges = [((df_max - df_min) * i) + df_min for i in bounds]
    styles = []
    legend = []
    backgroundColors = colorlover.scales[str(n_bins)]["div"]["RdYlGn"]
    backgroundColors.reverse()
    for i in range(1, len(bounds)):
        min_bound = ranges[i - 1]
        max_bound = ranges[i]
        backgroundColor = backgroundColors[i - 1]
        color = "white" if i > len(bounds) / 2.0 else "inherit"

        for column in df_numeric_columns:
            styles.append(
                {
                    "if": {
                        "filter_query": (
                            "{{{column}}} >= {min_bound}"
                            + (
                                " && {{{column}}} < {max_bound}"
                                if (i < len(bounds) - 1)
                                else ""
                            )
                        ).format(
                            column=column, min_bound=min_bound, max_bound=max_bound
                        ),
                        "column_id": column,
                    },
                    "backgroundColor": backgroundColor,
                    "color": color,
                }
            )
        legend.append(
            html.Div(
                style={"display": "inline-block", "width": "60px"},
                children=[
                    html.Div(
                        style={
                            "backgroundColor": backgroundColor,
                            "borderLeft": "1px rgb(50, 50, 50) solid",
                            "height": "10px",
                        }
                    ),
                    html.Small(round(min_bound, 2), style={"paddingLeft": "2px"}),
                ],
            )
        )

    return (styles, html.Div(legend, style={"padding": "5px 0 5px 0"}))


(styles, legend) = discrete_background_color_bins(df)

app.layout = html.Div(
    [
        html.Div(legend, style={"float": "right"}),
        dash_table.DataTable(
            data=df.to_dict("records"),
            sort_action="native",
            columns=[{"name": i, "id": i} for i in df.columns],
            style_data_conditional=styles,
        ),
    ]
)


if __name__ == "__main__":
    app.run_server(debug=True)

1 Like

Thank you @AnnMarieW It works like a charm !!!

Can I ask, I also tried a few other built-in colour scales, such as ‘YlOrRd’, and ‘Jet’, but I got KeyError .
Do you know why ? Thanks!

Hmm, it appears that those colors aren’t in the colorlover library, but you can find out more information here: GitHub - plotly/colorlover: Color scales in Python for humans

1 Like

Hi @AnnMarieW Could you have a look on my latest question. Many Thanks.

Hi @TristanSun

I’ll answer the question here since the code is in this post. To summarize you wanted extreme colors on each end in red and green in the middle.

This colorscale is not currently defined in the colorlover library, however you can provide your own colors for the scale. If you look at line 36 you will see where the background colors are generated with the colorlover utiity:

backgroundColors = colorlover.scales[str(n_bins)]["div"]["RdYlGn"]

If you print(backgroundColors) you will see that it returned a list of evenly spaced colors based on the “`RdYlGn’” colorscale

['rgb(215,48,39)', 'rgb(252,141,89)', 'rgb(254,224,139)', 'rgb(255,255,191)', 'rgb(217,239,139)', 'rgb(145,207,96)', 'rgb(26,152,80)']

You could rearrange the colors - or change them to whatever colors you like. For example if you used:

backgroundColors = ['rgb(215,48,39)', 'rgb(252,141,89)', 'rgb(145,207,96)', 'rgb(26,152,80)', 'rgb(145,207,96)', 'rgb(252,141,89)',  'rgb(215,48,39)']

The scale in the above example would be:

image

1 Like

Appreciate your prompt reply @AnnMarieW !
I tried to customize rgb color following your code.

I got this
image
I am not sure why all the colors are gone.

Previously the color legend was like this
image

Did I miss anything here?

I have imported the following lib

import colorsys
from collections import defaultdict
import math

Many Thanks

Hey @TristanSun I can’t tell what’s wrong based on what you posted. Can you provide a complete minimal example that I can copy and paste and try running?

1 Like

Thank you @AnnMarieW .
IT WAS My Fault.

It works like charm !
image

Appreciate !

1 Like

You’ll notice that the color in the legend also updates automatically!

1 Like