Skip to content

css technique for aligning decimals.ยค

all of my recent plotting techniques rely on access to cell values in css. when we have the values in css, along with their extent (min/max), we can adjust the values using math to align decimal points.

decimal point aligned is important in scanning documents and improving low numeracy experiences.

disclaimer: this technique use css log and sign. can i use sign indicates the function lacks broad support only works in firefox and safari which limits the application of this technique.

%%
our approach is to calculate the order of magnitude of our values and their sign on a monospace font.
we only need the maximum number of digits and signs before the decimal;
we'll refer to these values with the prefix `--tens`

    display\
```css
#aligned {
    font-family: monospace;
    --tens-max: calc(
        round(up, max(1, log(abs(var(--0-max)), 10)))
        + abs(min(0, sign(var(--0-max))))
    );
    --tens-min: calc(
        round(up, max(1, log(abs(var(--0-min)), 10)))
        + abs(min(0, sign(var(--0-min))))
    );
    --tens-total: calc(max(var(--tens-max), var(--tens-min)));
```
insert a pseudo-element in each table cell that using `1ch` spaces -
[Relative to the width of the "0" (zero)](https://www.w3schools.com/cssref/css_units.php) -

    display\
```css
#aligned td {
        text-align: left;    
        &::before {
            display: inline-block;
            content: "";
            --tens: calc(
                round(up, max(1, log(abs(var(--0)), 10)))
                + abs(min(0, sign(var(--0))))
            );
            width: calc(1ch * (var(--tens-total) - var(--tens)));
            padding: 0;
        }
    }
}
```

add a background grid for visual verification of the decimal alignment.

    display\
```css
#aligned {
    --w: 2px;
    position: relative;
    font-size: 36px;
    background: linear-gradient(90deg, transparent calc(0.25em - var(--w)), blue var(--w));
    background-size: 0.25em 100%;
    background-repeat: repeat-x;
    tr {
        background: unset;
    }
}
```

for each cell value we use the `::before` element to insert a single character width for 
each `--tens` unit. if padding is not removed then we'll end up with extra left whitespace.
we overlay a visual grid for verification.

    display\
```css
#aligned td {
    text-align: left;    
    &::before {
        display: inline-block;
        content: "";
        --tens: calc(
            round(up, max(1, log(abs(var(--0)), 10)))
            + abs(min(0, sign(var(--0))))
        );
        width: calc(1ch * (var(--tens-total) - var(--tens)));
        padding: 0;
    }
}
```
    import nbconvert_a11y.table
    (df := DataFrame(
        numpy.random.randn(30, 1)
    ).pow(9))



    df.table(id="aligned")
0
0 0.000
1 679.516
2 89.413
3 -59.087
4 -0.006
5 6.528
6 -0.000
7 24.380
8 -3769.932
9 0.000
10 -0.070
11 -0.000
12 0.020
13 -0.009
14 0.321
15 -1.956
16 0.001
17 0.152
18 0.000
19 -5.123
20 0.100
21 0.000
22 -0.034
23 0.000
24 0.000
25 -0.000
26 -0.000
27 -0.001
28 0.000
29 0.000
min -3769.932
max 679.516
controls

context

our approach is to calculate the order of magnitude of our values and their sign on a monospace font. we only need the maximum number of digits and signs before the decimal; we'll refer to these values with the prefix --tens

display\
#aligned {
    font-family: monospace;
    --tens-max: calc(
        round(up, max(1, log(abs(var(--0-max)), 10)))
        + abs(min(0, sign(var(--0-max))))
    );
    --tens-min: calc(
        round(up, max(1, log(abs(var(--0-min)), 10)))
        + abs(min(0, sign(var(--0-min))))
    );
    --tens-total: calc(max(var(--tens-max), var(--tens-min)));

insert a pseudo-element in each table cell that using 1ch spaces - Relative to the width of the "0" (zero) -

display\
#aligned td {
        text-align: left;    
        &::before {
            display: inline-block;
            content: "";
            --tens: calc(
                round(up, max(1, log(abs(var(--0)), 10)))
                + abs(min(0, sign(var(--0))))
            );
            width: calc(1ch * (var(--tens-total) - var(--tens)));
            padding: 0;
        }
    }
}

add a background grid for visual verification of the decimal alignment.

display\
#aligned {
    --w: 2px;
    position: relative;
    font-size: 36px;
    background: linear-gradient(90deg, transparent calc(0.25em - var(--w)), blue var(--w));
    background-size: 0.25em 100%;
    background-repeat: repeat-x;
    tr {
        background: unset;
    }
}

for each cell value we use the ::before element to insert a single character width for each --tens unit. if padding is not removed then we'll end up with extra left whitespace. we overlay a visual grid for verification.

display\
#aligned td {
    text-align: left;    
    &::before {
        display: inline-block;
        content: "";
        --tens: calc(
            round(up, max(1, log(abs(var(--0)), 10)))
            + abs(min(0, sign(var(--0))))
        );
        width: calc(1ch * (var(--tens-total) - var(--tens)));
        padding: 0;
    }
}
import nbconvert_a11y.table
(df := DataFrame(
    numpy.random.randn(30, 1)
).pow(9))



df.table(id="aligned")