Black Lives Matter. Please consider donating to Black Girls Code today.

Sparklines as Fonts! Embedding Minimal Sparklines in Tables & Components

:wave: Hey everyone – I just wanted share a really cool project that we came across today: https://github.com/aftertheflood/sparks

That project creates custom font families that render sets of numbers as simple bar chart and line charts. We’re not affiliated with the project, but huge fans of the approach!

Rendering simple charts like this as fonts is really great for Dash because it means that you can embed these sparklines almost anywhere in your apps. I immediately thought of the DataTable, especially after embedding things like emojis in cells in the conditional formatting chapter.

Here’s an example of a simple app:
image

import dash
import dash_html_components as html
import dash_table
import dash_core_components as dcc

app = dash.Dash(__name__)

app.layout = html.Div([

    html.H1(
        className='sparks dotline-extrathick',
        children='123{30,60,90,60,100,50,45,20}456'
    ),

    html.Div(className='sparks dotline-extrathick', children=[
        dash_table.DataTable(
            columns=[
                {'id': 'Summary', 'name': 'Summary'},
                {'id': 'Overview', 'name': 'Overview'},
            ],
            data=[
                {
                    'Summary': '123{30,60,90,60,100,50,45,20}456',
                    'Overview': i
                } for i in range(20)
            ],
            style_data_conditional=[
                {
                    'if': {'column_id': 'Summary'},
                    'width': 100
                }
            ]
        ),
    ]),


])

app.run_server(debug=True)

Which uses this CSS (note these font familes are hosted by a third party, so don’t use this in any production apps! You never know when aftertheflood.com might go down and we should respect their bandwidth)

@font-face {
  font-family: 'Sparks-Bar-Narrow';
  src: url('https://tools.aftertheflood.com/sparks/output/eot/Sparks-Bar-Narrow.eot');
  src: url('https://tools.aftertheflood.com/sparks/output/eot/Sparks-Bar-Narrow.eot?#iefix') format('embedded-opentype'),
       url('https://tools.aftertheflood.com/sparks/output/woff2/Sparks-Bar-Narrow.woff2') format('woff2'),
       url('https://tools.aftertheflood.com/sparks/output/woff/Sparks-Bar-Narrow.woff') format('woff'),
       url('https://tools.aftertheflood.com/sparks/output/ttf/Sparks-Bar-Narrow.ttf') format('truetype'),
       url('https://tools.aftertheflood.com/sparks/output/svg/Sparks-Bar-Narrow.svg#Sparks-Bar-Narrow') format('svg');
  font-weight: normal;
  font-style: normal;
}

@font-face {
  font-family: 'Sparks-Bar-Medium';
  src: url('https://tools.aftertheflood.com/sparks/output/eot/Sparks-Bar-Medium.eot');
  src: url('https://tools.aftertheflood.com/sparks/output/eot/Sparks-Bar-Medium.eot?#iefix') format('embedded-opentype'),
       url('https://tools.aftertheflood.com/sparks/output/woff2/Sparks-Bar-Medium.woff2') format('woff2'),
       url('https://tools.aftertheflood.com/sparks/output/woff/Sparks-Bar-Medium.woff') format('woff'),
       url('https://tools.aftertheflood.com/sparks/output/ttf/Sparks-Bar-Medium.ttf') format('truetype'),
       url('https://tools.aftertheflood.com/sparks/output/svg/Sparks-Bar-Medium.svg#Sparks-Bar-Medium') format('svg');
  font-weight: normal;
  font-style: normal;
}

@font-face {
  font-family: 'Sparks-Bar-Wide';
  src: url('https://tools.aftertheflood.com/sparks/output/eot/Sparks-Bar-Wide.eot');
  src: url('https://tools.aftertheflood.com/sparks/output/eot/Sparks-Bar-Wide.eot?#iefix') format('embedded-opentype'),
       url('https://tools.aftertheflood.com/sparks/output/woff2/Sparks-Bar-Wide.woff2') format('woff2'),
       url('https://tools.aftertheflood.com/sparks/output/woff/Sparks-Bar-Wide.woff') format('woff'),
       url('https://tools.aftertheflood.com/sparks/output/ttf/Sparks-Bar-Wide.ttf') format('truetype'),
       url('https://tools.aftertheflood.com/sparks/output/svg/Sparks-Bar-Wide.svg#Sparks-Bar-Wide') format('svg');
  font-weight: normal;
  font-style: normal;
}

@font-face {
  font-family: 'Sparks-Bar-Extrawide';
  src: url('https://tools.aftertheflood.com/sparks/output/eot/Sparks-Bar-Extrawide.eot');
  src: url('https://tools.aftertheflood.com/sparks/output/eot/Sparks-Bar-Extrawide.eot?#iefix') format('embedded-opentype'),
       url('https://tools.aftertheflood.com/sparks/output/woff2/Sparks-Bar-Extrawide.woff2') format('woff2'),
       url('https://tools.aftertheflood.com/sparks/output/woff/Sparks-Bar-Extrawide.woff') format('woff'),
       url('https://tools.aftertheflood.com/sparks/output/ttf/Sparks-Bar-Extrawide.ttf') format('truetype'),
       url('https://tools.aftertheflood.com/sparks/output/svg/Sparks-Bar-Extrawide.svg#Sparks-Bar-Extrawide') format('svg');
  font-weight: normal;
  font-style: normal;
}

/* Dots */

@font-face {
  font-family: 'Sparks-Dot-Extrasmall';
  src: url('https://tools.aftertheflood.com/sparks/output/eot/Sparks-Dot-Extrasmall.eot');
  src: url('https://tools.aftertheflood.com/sparks/output/eot/Sparks-Dot-Extrasmall.eot?#iefix') format('embedded-opentype'),
       url('https://tools.aftertheflood.com/sparks/output/woff2/Sparks-Dot-Extrasmall.woff2') format('woff2'),
       url('https://tools.aftertheflood.com/sparks/output/woff/Sparks-Dot-Extrasmall.woff') format('woff'),
       url('https://tools.aftertheflood.com/sparks/output/ttf/Sparks-Dot-Extrasmall.ttf') format('truetype'),
       url('https://tools.aftertheflood.com/sparks/output/svg/Sparks-Dot-Extrasmall.svg#Sparks-Dot-Extrasmall') format('svg');
  font-weight: normal;
  font-style: normal;
}

@font-face {
  font-family: 'Sparks-Dot-Small';
  src: url('https://tools.aftertheflood.com/sparks/output/eot/Sparks-Dot-Small.eot');
  src: url('https://tools.aftertheflood.com/sparks/output/eot/Sparks-Dot-Small.eot?#iefix') format('embedded-opentype'),
       url('https://tools.aftertheflood.com/sparks/output/woff2/Sparks-Dot-Small.woff2') format('woff2'),
       url('https://tools.aftertheflood.com/sparks/output/woff/Sparks-Dot-Small.woff') format('woff'),
       url('https://tools.aftertheflood.com/sparks/output/ttf/Sparks-Dot-Small.ttf') format('truetype'),
       url('https://tools.aftertheflood.com/sparks/output/svg/Sparks-Dot-Small.svg#Sparks-Dot-Small') format('svg');
  font-weight: normal;
  font-style: normal;
}

@font-face {
  font-family: 'Sparks-Dot-Medium';
  src: url('https://tools.aftertheflood.com/sparks/output/eot/Sparks-Dot-Medium.eot');
  src: url('https://tools.aftertheflood.com/sparks/output/eot/Sparks-Dot-Medium.eot?#iefix') format('embedded-opentype'),
       url('https://tools.aftertheflood.com/sparks/output/woff2/Sparks-Dot-Medium.woff2') format('woff2'),
       url('https://tools.aftertheflood.com/sparks/output/woff/Sparks-Dot-Medium.woff') format('woff'),
       url('https://tools.aftertheflood.com/sparks/output/ttf/Sparks-Dot-Medium.ttf') format('truetype'),
       url('https://tools.aftertheflood.com/sparks/output/svg/Sparks-Dot-Medium.svg#Sparks-Dot-Medium') format('svg');
  font-weight: normal;
  font-style: normal;
}

@font-face {
  font-family: 'Sparks-Dot-Large';
  src: url('https://tools.aftertheflood.com/sparks/output/eot/Sparks-Dot-Large.eot');
  src: url('https://tools.aftertheflood.com/sparks/output/eot/Sparks-Dot-Large.eot?#iefix') format('embedded-opentype'),
       url('https://tools.aftertheflood.com/sparks/output/woff2/Sparks-Dot-Large.woff2') format('woff2'),
       url('https://tools.aftertheflood.com/sparks/output/woff/Sparks-Dot-Large.woff') format('woff'),
       url('https://tools.aftertheflood.com/sparks/output/ttf/Sparks-Dot-Large.ttf') format('truetype'),
       url('https://tools.aftertheflood.com/sparks/output/svg/Sparks-Dot-Large.svg#Sparks-Dot-Large') format('svg');
  font-weight: normal;
  font-style: normal;
}

@font-face {
  font-family: 'Sparks-Dot-Extralarge';
  src: url('https://tools.aftertheflood.com/sparks/output/eot/Sparks-Dot-Extralarge.eot');
  src: url('https://tools.aftertheflood.com/sparks/output/eot/Sparks-Dot-Extralarge.eot?#iefix') format('embedded-opentype'),
       url('https://tools.aftertheflood.com/sparks/output/woff2/Sparks-Dot-Extralarge.woff2') format('woff2'),
       url('https://tools.aftertheflood.com/sparks/output/woff/Sparks-Dot-Extralarge.woff') format('woff'),
       url('https://tools.aftertheflood.com/sparks/output/ttf/Sparks-Dot-Extralarge.ttf') format('truetype'),
       url('https://tools.aftertheflood.com/sparks/output/svg/Sparks-Dot-Extralarge.svg#Sparks-Dot-Extralarge') format('svg');
  font-weight: normal;
  font-style: normal;
}

/* Dot-lines */

@font-face {
  font-family: 'Sparks-Dotline-Extrathin';
  src: url('https://tools.aftertheflood.com/sparks/output/eot/Sparks-Dotline-Extrathin.eot');
  src: url('https://tools.aftertheflood.com/sparks/output/eot/Sparks-Dotline-Extrathin.eot?#iefix') format('embedded-opentype'),
       url('https://tools.aftertheflood.com/sparks/output/woff2/Sparks-Dotline-Extrathin.woff2') format('woff2'),
       url('https://tools.aftertheflood.com/sparks/output/woff/Sparks-Dotline-Extrathin.woff') format('woff'),
       url('https://tools.aftertheflood.com/sparks/output/ttf/Sparks-Dotline-Extrathin.ttf') format('truetype'),
       url('https://tools.aftertheflood.com/sparks/output/svg/Sparks-Dotline-Extrathin.svg#Sparks-Dotline-Extrathin') format('svg');
  font-weight: normal;
  font-style: normal;
}

@font-face {
  font-family: 'Sparks-Dotline-Thin';
  src: url('https://tools.aftertheflood.com/sparks/output/eot/Sparks-Dotline-Thin.eot');
  src: url('https://tools.aftertheflood.com/sparks/output/eot/Sparks-Dotline-Thin.eot?#iefix') format('embedded-opentype'),
       url('https://tools.aftertheflood.com/sparks/output/woff2/Sparks-Dotline-Thin.woff2') format('woff2'),
       url('https://tools.aftertheflood.com/sparks/output/woff/Sparks-Dotline-Thin.woff') format('woff'),
       url('https://tools.aftertheflood.com/sparks/output/ttf/Sparks-Dotline-Thin.ttf') format('truetype'),
       url('https://tools.aftertheflood.com/sparks/output/svg/Sparks-Dotline-Thin.svg#Sparks-Dotline-Thin') format('svg');
  font-weight: normal;
  font-style: normal;
}

@font-face {
  font-family: 'Sparks-Dotline-Medium';
  src: url('https://tools.aftertheflood.com/sparks/output/eot/Sparks-Dotline-Medium.eot');
  src: url('https://tools.aftertheflood.com/sparks/output/eot/Sparks-Dotline-Medium.eot?#iefix') format('embedded-opentype'),
       url('https://tools.aftertheflood.com/sparks/output/woff/2Sparks-Dotline-Medium.woff2') format('woff2'),
       url('https://tools.aftertheflood.com/sparks/output/woff/Sparks-Dotline-Medium.woff') format('woff'),
       url('https://tools.aftertheflood.com/sparks/output/ttf/Sparks-Dotline-Medium.ttf') format('truetype'),
       url('https://tools.aftertheflood.com/sparks/output/svg/Sparks-Dotline-Medium.svg#Sparks-Dotline-Medium') format('svg');
  font-weight: normal;
  font-style: normal;
}

@font-face {
  font-family: 'Sparks-Dotline-Thick';
  src: url('https://tools.aftertheflood.com/sparks/output/eot/Sparks-Dotline-Thick.eot');
  src: url('https://tools.aftertheflood.com/sparks/output/eot/Sparks-Dotline-Thick.eot?#iefix') format('embedded-opentype'),
       url('https://tools.aftertheflood.com/sparks/output/woff2/Sparks-Dotline-Thick.woff2') format('woff2'),
       url('https://tools.aftertheflood.com/sparks/output/woff/Sparks-Dotline-Thick.woff') format('woff'),
       url('https://tools.aftertheflood.com/sparks/output/ttf/Sparks-Dotline-Thick.ttf') format('truetype'),
       url('https://tools.aftertheflood.com/sparks/output/svg/Sparks-Dotline-Thick.svg#Sparks-Dotline-Thick') format('svg');
  font-weight: normal;
  font-style: normal;
}

@font-face {
  font-family: 'Sparks-Dotline-Extrathick';
  src: url('https://tools.aftertheflood.com/sparks/output/eot/Sparks-Dotline-Extrathick.eot');
  src: url('https://tools.aftertheflood.com/sparks/output/eot/Sparks-Dotline-Extrathick.eot?#iefix') format('embedded-opentype'),
       url('https://tools.aftertheflood.com/sparks/output/woff2/Sparks-Dotline-Extrathick.woff2') format('woff2'),
       url('https://tools.aftertheflood.com/sparks/output/woff/Sparks-Dotline-Extrathick.woff') format('woff'),
       url('https://tools.aftertheflood.com/sparks/output/ttf/Sparks-Dotline-Extrathick.ttf') format('truetype'),
       url('https://tools.aftertheflood.com/sparks/output/svg/Sparks-Dotline-Extrathick.svg#Sparks-Dotline-Extrathick') format('svg');
  font-weight: normal;
  font-style: normal;
}

.sparks {
  font-variant-ligatures: normal;
}

.bar-narrow {
  font-family: Sparks-Bar-Medium;
}
.bar-medium {
  font-family: Sparks-Bar-Medium;
}
.bar-wide {
  font-family: Sparks-Bar-Wide;
}
.bar-extrawide {
  font-family: Sparks-Bar-Wide;
}

.dot-extrasmall {
  font-family: Sparks-Dot-Extrasmall;
}
.dot-small {
  font-family: Sparks-Dot-Small;
}
.dot-medium {
  font-family: Sparks-Dot-Medium;
}
.dot-large {
  font-family: Sparks-Dot-Large;
}
.dot-extralarge {
  font-family: Sparks-Dot-Extralarge;
}

.dotline-extrathin {
  font-family: Sparks-Dotline-Extrathin;
}
.dotline-thin {
  font-family: Sparks-Dotline-Thin;
}
.dotline-medium {
  font-family: Sparks-Dotline-Medium;
}
.dotline-thick {
  font-family: Sparks-Dotline-Thick;
}
.dotline-extrathick {
  font-family: Sparks-Dotline-Extrathick;
}

.final-value{
  color:#F00;
}

It’s a very cool approach and there some other projects out there that do similar things, like “Blazor Sparklines” https://github.com/Misfits-Rebels-Outcasts/Blazor-Sparkline. For more, try googling “Sparklines as fonts”

It would be great to get some helpful examples of this approach into the conditional formatting documentation. If anyone would like to contribute to Dash, here is the source code to that chapter: https://github.com/plotly/dash-docs/blob/master/dash_docs/chapters/dash_datatable/conditional_formatting/index.py

Enjoy! And if you make something with these fonts, please do share :smile:

5 Likes

Does it work? I tried it but it didn’t work.
I copied the same code and put the css under assets folder, is there anything i did wrong?

Hi @leow

When you run the code, do you see the sparkline in the heading (ie from the html.H1 component) but not in the table?

If so, try adding the font-family to the style_data_conditional:

style_data_conditional=[
                {
                    'if': {'column_id': 'Summary'},
                    'width': 100,
                    'font-family': 'Sparks-Bar-Wide'
                }
            ]

If you didn’t see any sparkline at all, its probably not finding the css in the assets folder.