Skip to content

3d plots from html tables¤

lets use html and html to try to make a 3d plot. most plotting is an ends where the table is the means. if we can generate plots from tabular data then we could encourage more complete tables in scientific literature.

%%

    shell.environment.globals.update(vars(__builtins__))
%%
## making sample data

create some randomized datasets with distinct clusters

    import sklearn.datasets, sklearn.discriminant_analysis, sklearn.model_selection
    features = 6
    df = pandas.DataFrame(*sklearn.datasets.make_blobs(200, features, centers=[
        (i**2,)*features for i in range(features)
    ]))

    df = pandas.concat(dict(zip("test train".split(), sklearn.model_selection.train_test_split(df))))
    df = df.rename_axis(index="selection blob".split())

apply statistical analysis to reduce the dimensionality of the data.
a sample of the full and reduced dataset are shown next, with the source as a caption.

<figure>
{{df.sample(2).style.to_html()}}
<figcaption>

    model = sklearn.discriminant_analysis.LinearDiscriminantAnalysis(n_components=3)
    model.fit(df.loc["test"], df.loc["test"].index)
    df = df.combine_first(
        pandas.DataFrame(model.transform(df.values), columns=list("xyz"), index=df.index)
    )
    df = pandas.concat([df, pandas.DataFrame([[-1]*features+ [0,0,0]] , pandas.MultiIndex.from_tuples([("test", -1)]), df.columns)])

</figcaption></figure>

making sample data

create some randomized datasets with distinct clusters

import sklearn.datasets, sklearn.discriminant_analysis, sklearn.model_selection
features = 6
df = pandas.DataFrame(*sklearn.datasets.make_blobs(200, features, centers=[
    (i**2,)*features for i in range(features)
]))

df = pandas.concat(dict(zip("test train".split(), sklearn.model_selection.train_test_split(df))))
df = df.rename_axis(index="selection blob".split())

apply statistical analysis to reduce the dimensionality of the data. a sample of the full and reduced dataset are shown next, with the source as a caption.

    0 1 2 3 4 5 x y z
test 3 9.830273 9.151163 11.189289 9.057340 8.674574 8.675005 1.104807 -0.331329 -0.317266
1 0.111571 1.710573 0.153907 2.878302 1.887411 0.045487 -22.539861 -1.035561 -1.366237
model = sklearn.discriminant_analysis.LinearDiscriminantAnalysis(n_components=3)
model.fit(df.loc["test"], df.loc["test"].index)
df = df.combine_first(
    pandas.DataFrame(model.transform(df.values), columns=list("xyz"), index=df.index)
)
df = pandas.concat([df, pandas.DataFrame([[-1]*features+ [0,0,0]] , pandas.MultiIndex.from_tuples([("test", -1)]), df.columns)])
%%
## generate a 3d plot in an image for comparsion

we'll compare our interactive css outcomes with rendered html image.

    import matplotlib.pyplot
    fig = matplotlib.pyplot.figure()
    ax = fig.add_subplot(projection="3d")
    df[list("xyz")].apply(lambda x: ax.scatter(*x.values, c=x.name[1]), axis=1);

generate a 3d plot in an image for comparsion

we'll compare our interactive css outcomes with rendered html image.

import matplotlib.pyplot
fig = matplotlib.pyplot.figure()
ax = fig.add_subplot(projection="3d")
df[list("xyz")].apply(lambda x: ax.scatter(*x.values, c=x.name[1]), axis=1);
No description has been provided for this image
%%
## creating our table

    table =\
{% set format="%.2f" %}
<table id="pca" role="table">
<tbody role="rowgroup" style="--x-min: {{df.x.min()}}; --x-max: {{df.x.max()}}; --y-min: {{df.y.min()}}; --y-max: {{df.y.max()}}; 
    --z-min: {{df.z.min()}}; --z-max: {{df.z.max()}};">
    {%- for i, (_, row) in enumerate(df.iterrows()) -%}
        <tr role="row" style="--x: {{row.loc['x']}}; --y: {{row.loc['y']}}; --z: {{row.loc['z']}};">
<th role="rowheader">{{i}}</th>
        {%- for j in range(features) -%}
        <td role="cell">{{format % row.loc[j]}}</td>
        {%- endfor %}{% for j in "xyz" -%}
        <td hidden="" role="cell">{{ row.loc[j]}}</td>
        {%- endfor %}</tr>
    {%- endfor %}</tbody>
</table>




    form =\
<form name="controls">
<script>
    function rotate(axis, el){
        document.getElementById("pca").style.setProperty(`--rotate-${axis}`, `${el.value}deg`);
    }
    </script>
<label>x
    <input max="360" min="0" name="x" oninput="rotate('x', this)" step="5" type="range" value="350"/>
</label>
<label>y
    <input max="360" min="0" name="y" oninput="rotate('y', this)" step="5" type="range" value="5"/>
</label>
<label>z
    <input max="360" min="0" name="z" oninput="rotate('z', this)" step="5" type="range" value="15"/>
</label>
<label>show table<input checked="" name="show" onchange="document.getElementById('pca-style').setAttribute('media', this.checked ? 'screen': 'none')" type="checkbox"/></label>
</form>
<details>
<summary>html table source</summary>

```html
{{table}}
```

 </details>
<details>
<summary>form source</summary>


```html
{{form}}
```

 </details>

creating our table

table =\
04.642.664.033.562.233.19
1-0.400.90-0.060.89-0.69-0.38
2-0.450.18-2.10-0.380.54-1.37
316.2117.3315.4517.2715.9715.45
47.5310.529.069.568.668.73
50.28-0.05-0.240.080.020.21
615.9415.4516.6714.3716.2815.61
70.121.740.100.600.150.76
815.7517.2114.9615.6314.0717.78
98.7110.977.899.419.329.49
1024.1325.2425.3424.8423.1526.47
118.448.278.169.039.619.10
1214.3315.7116.9813.7115.5216.83
1323.6025.4923.6925.8524.5224.45
1415.4717.2215.5617.0113.3415.80
150.87-1.341.312.122.560.11
167.967.968.958.847.7410.23
175.343.445.434.033.905.23
1825.1526.9924.3424.6226.1624.42
1924.7024.7925.8425.6924.3725.85
2024.6425.2226.1125.7026.4526.19
2115.6117.2816.0616.4216.7416.64
224.055.224.003.714.623.99
2325.2325.3726.4025.1925.1225.74
240.51-0.011.140.69-0.010.72
2516.5615.8816.0914.9715.5914.16
263.136.062.684.424.784.42
2714.4717.1516.4815.2418.2014.89
289.237.909.899.149.067.84
291.041.520.921.38-0.451.32
30-0.010.711.840.11-1.282.03
315.514.332.773.523.714.89
322.663.723.272.513.494.31
33-0.440.64-1.21-0.63-1.24-1.23
3425.3325.1224.4827.2324.4325.07
3517.7516.1416.2516.4215.7516.28
361.220.131.962.610.641.91
3724.3323.8727.2326.2324.9523.57
3816.0515.6816.6516.2916.0817.54
3916.8016.3814.0315.6917.1516.62
409.839.1511.199.068.678.68
4117.1715.0015.9016.0916.7817.17
4225.4026.4124.4624.8223.6125.79
439.387.959.869.128.3710.35
448.759.018.249.418.909.90
458.856.079.589.938.1810.17
4626.1125.7524.4725.4324.6124.04
471.291.340.051.202.710.82
4816.2916.5914.4915.5015.6117.31
490.91-0.130.881.741.382.78
504.175.435.045.192.733.78
518.077.2310.8111.197.927.74
520.44-1.060.681.540.60-0.03
5316.4916.6915.6215.7716.3416.91
54-2.27-0.380.70-1.480.730.13
558.889.939.068.258.829.37
569.339.038.898.479.7610.15
579.168.436.909.568.5810.57
589.639.919.8111.218.738.13
5915.1815.8016.2716.5216.9716.95
607.627.9111.739.249.619.03
612.171.35-0.181.530.670.01
6216.8417.3716.9113.5917.1216.28
63-0.762.350.330.730.03-0.07
6424.9123.6324.1522.5324.4026.12
6523.8524.5624.7525.1024.8224.84
668.128.739.866.238.0410.16
6724.4026.1223.9922.1825.7024.59
683.654.473.692.614.073.62
6910.589.959.188.919.388.14
702.40-0.061.041.24-0.090.26
714.632.833.833.994.134.08
7216.3116.5916.2916.8516.0415.22
735.014.424.385.244.274.43
740.111.710.152.881.890.05
753.232.473.942.875.695.06
765.215.504.263.653.754.08
77-0.21-1.87-0.35-0.070.54-0.62
789.188.969.959.6110.177.38
7923.4824.8226.3724.8924.7026.22
801.18-1.39-0.02-0.301.411.22
8115.6316.1015.9516.8015.4216.64
82-0.90-0.30-0.83-2.160.08-0.00
839.769.958.6310.359.628.27
8425.7425.3024.2622.6425.9226.94
85-1.53-0.210.26-1.46-0.04-0.82
8614.9517.0417.2215.7516.6717.09
8725.0024.2926.7824.7925.4427.19
882.031.110.261.651.950.27
89-0.610.25-0.14-0.070.36-0.64
903.684.724.132.854.844.22
91-1.311.460.400.760.21-1.59
92-0.394.182.550.470.410.22
9324.8626.2425.3723.5725.3724.52
94-0.650.360.201.17-0.21-0.68
95-0.482.063.722.381.302.39
9624.5325.9922.9525.3824.4324.60
974.224.275.294.593.965.94
980.070.41-0.260.444.841.35
990.301.09-0.811.091.22-1.04
1005.114.893.674.332.304.70
1010.170.341.61-0.40-1.74-1.17
1022.191.17-1.001.092.11-0.46
103-0.280.780.92-0.86-1.12-0.39
1043.230.98-0.922.241.562.54
10515.8117.7716.6015.4616.1816.04
10624.4023.8424.4223.6425.3825.11
10725.1723.0625.9323.8325.0526.29
108-1.26-0.25-1.301.260.45-0.02
1094.074.913.493.354.915.43
1108.3910.899.0911.5710.169.41
1111.800.071.570.731.431.31
112-0.200.310.240.85-0.730.53
1130.900.201.111.071.990.14
11424.3726.3124.1725.6426.3324.51
115-0.871.73-0.381.070.480.17
1164.083.513.314.533.715.39
117-1.770.49-1.460.611.240.06
118-0.96-0.61-0.38-1.47-1.310.65
1190.79-0.70-0.71-0.091.01-0.52
12016.5216.5715.6415.6216.3615.94
1215.444.634.014.514.622.83
12216.5316.9317.1516.6314.7416.13
123-0.090.201.02-1.72-0.570.25
12416.8916.5016.2416.9614.3516.78
1251.530.42-0.590.851.392.42
1267.968.558.439.978.588.97
12710.037.9111.147.367.798.58
12814.5917.7216.2315.9715.8615.95
1297.309.908.1810.049.067.72
13011.298.727.879.2710.217.29
1312.753.833.962.725.244.08
132-1.82-1.451.32-0.520.38-0.49
1333.603.426.283.983.764.20
1344.042.863.963.824.372.35
1357.799.0510.648.387.9610.99
13610.4410.279.407.957.849.29
1378.858.688.749.819.179.30
1383.333.653.412.464.335.27
1393.173.693.944.903.644.44
14025.7824.6222.8226.8424.7024.24
1413.513.744.893.233.774.41
1420.69-1.290.231.02-1.82-1.24
1431.540.700.29-0.690.551.85
144-0.110.93-0.790.81-0.04-0.29
14525.9625.1325.8724.4823.6324.93
1460.741.570.940.910.650.12
14716.2614.8316.9713.4617.1615.29
1484.855.782.665.953.624.29
14926.3424.7523.1024.2625.5126.16
1504.783.552.822.994.023.56
15124.3126.1325.6724.4723.9525.87
15227.2324.6625.1325.1424.4024.49
1532.975.744.874.473.674.25
15414.0718.0415.5415.7816.6015.29
1552.744.533.675.556.333.48
1562.732.23-0.830.750.950.92
1573.325.554.904.763.755.32
1588.529.959.547.138.238.29
1591.970.440.93-0.57-0.181.71
1609.278.3410.219.198.607.39
161-0.560.391.26-0.66-1.860.03
16225.1025.6525.5726.4424.6425.87
1630.880.214.41-0.451.321.54
16422.4025.8325.1123.8424.3625.74
165-0.120.54-0.601.09-1.440.54
1660.05-0.16-0.18-1.32-0.01-1.08
167-0.683.14-1.020.771.04-0.23
1688.639.9010.437.898.788.95
1693.412.096.914.794.462.87
170-0.420.930.79-0.73-0.080.55
1713.722.994.844.704.312.13
17224.9524.4726.4325.5226.0024.94
17315.9215.7913.9914.6917.8415.78
17416.7616.3614.8015.7015.1114.68
1753.030.010.300.411.771.17
1768.189.339.668.898.787.48
1774.236.103.824.515.302.99
1787.768.7910.108.358.298.56
17916.0616.3215.5616.2315.1416.03
180-0.612.332.530.16-0.552.65
181-0.550.421.19-0.26-0.59-1.92
1820.941.042.522.340.372.03
1831.120.292.401.221.360.19
18416.6516.4316.3516.6216.6218.20
18524.2424.0225.3525.1123.1025.53
1867.707.8611.139.699.089.59
18715.5215.6915.8116.3815.3215.71
18825.3423.4626.4125.2324.5324.83
1890.30-0.500.740.31-1.130.67
1902.090.490.970.750.111.15
19116.3116.5516.7317.4715.4217.27
1922.493.081.540.160.371.87
19325.9524.4524.6325.0423.7725.21
1940.29-0.830.58-1.330.200.76
19524.6123.8125.8126.5526.7026.65
19614.0916.3017.4417.1918.3515.33
1970.741.921.042.291.95-0.02
1983.543.543.873.473.955.37
1991.350.990.351.211.141.28
200-1.00-1.00-1.00-1.00-1.00-1.00
form =\
html table source
{% set format="%.2f" %}
<table role=table id=pca>
    <tbody role=rowgroup style="--x-min: {{df.x.min()}}; --x-max: {{df.x.max()}}; --y-min: {{df.y.min()}}; --y-max: {{df.y.max()}}; 
    --z-min: {{df.z.min()}}; --z-max: {{df.z.max()}};">
    {%- for i, (_, row) in enumerate(df.iterrows()) -%}
        <tr role=row style="--x: {{row.loc['x']}}; --y: {{row.loc['y']}}; --z: {{row.loc['z']}};">
        <th role=rowheader>{{i}}</th>
        {%- for j in range(features) -%}
        <td role=cell>{{format % row.loc[j]}}</td>
        {%- endfor %}{% for j in "xyz" -%}
        <td role=cell hidden>{{ row.loc[j]}}</td>
        {%- endfor %}</tr>
    {%- endfor %}</tbody>
</table>
form source
<form name=controls>
    <script>
    function rotate(axis, el){
        document.getElementById("pca").style.setProperty(`--rotate-${axis}`, `${el.value}deg`);
    }
    </script>
    <label>x
    <input type=range min=0 max=360 value=350 step=5 oninput="rotate('x', this)" name="x"/>
    </label>
    <label>y
    <input type=range min=0 max=360 value=5 step=5 oninput="rotate('y', this)" name="y"/>
    </label>
    <label>z
    <input type=range min=0 max=360 value=15 step=5 oninput="rotate('z', this)" name="z"/>
    </label>
    <label>show table<input type=checkbox name=show checked onchange="document.getElementById('pca-style').setAttribute('media', this.checked ? 'screen': 'none')"/></label>
</form>

<details>
<summary>html table source</summary>
%%
## styling the table

style the table using 3d css transform, rotations, and translations.

    style =\
<style id="pca-style" media="screen">
.jp-RenderedHTMLCommon tbody tr:hover {
    background: unset;
}
#pca {
    --height: 600px;
    --width: 600px;
    --depth: 600px;
    --rotate-x: 0deg;
    --rotate-y: 0deg;
    --rotate-z: 0deg;
    display: block;
    position: relative;
    width: var(--width);
    height: var(--height);
    margin-left: auto;
    margin-right: auto;
    tbody{
        perspective: .3;
        border: solid;
        position: absolute;
        display: block;
        top: 0;
        left: 0;
        width: var(--width);
        height: var(--height);
        --dx: calc(var(--x-max) - var(--x-min));
        --dy: calc(var(--y-max) - var(--y-min));
        --dz: calc(var(--z-max) - var(--z-min));
        --x-origin: calc(-1 * var(--x-min) / var(--dx) * var(--width));
        --y-origin: calc(var(--y-max) / var(--dy) * var(--height));
        transform-style: preserve-3d;
    }
    tr {        
        top: 0;
        left: 0;
        position: absolute;
        display: inline-block;
        transform-origin: var(--x-origin) var(--y-origin) 0;
        transform: 
            translateX(var(--x-origin)) translateY(var(--y-origin))
            rotateZ(calc(360deg - var(--rotate-z))) 
            rotateY(calc(360deg - var(--rotate-y)))  
            rotateX(calc(360deg - var(--rotate-x)))
            translateX(calc(var(--x) / var(--dx) * var(--width)))
            translateY(calc(-1 * var(--y) / var(--dy) * var(--height)))
            translateZ(calc(var(--z) / var(--dz) * var(--depth)))
            rotateX(var(--rotate-x)) rotateY(var(--rotate-y)) rotateZ(var(--rotate-z));
        td {
            display: none;
        }
    }
    tr:hover, tr:focus-within {
         transform: 
            translateX(var(--x-origin)) translateY(var(--y-origin))
            rotateZ(calc(360deg - var(--rotate-z))) 
            rotateY(calc(360deg - var(--rotate-y)))  
            rotateX(calc(360deg - var(--rotate-x)))
            translateX(calc(var(--x) / var(--dx) * var(--width)))
            translateY(calc(-1 * var(--y) / var(--dy) * var(--height)))
            translateZ(calc(var(--z) / var(--dz) * var(--depth)))
            rotateX(var(--rotate-x)) rotateY(var(--rotate-y)) rotateZ(var(--rotate-z))
            translateZ(10000px);
        th, td:not([hidden]) {
            display: unset;
        }
    }
    tr:last-of-type {
        background-color: red;
    }
}
</style>

    ...
<details open="">
<summary>css with 3d translations, transformations, and rotations</summary>

```html
{{style}}
```

</details>


    ...;

styling the table

style the table using 3d css transform, rotations, and translations.

style =\
...
css with 3d translations, transformations, and rotations
<style id=pca-style media=screen>
.jp-RenderedHTMLCommon tbody tr:hover {
    background: unset;
}
#pca {
    --height: 600px;
    --width: 600px;
    --depth: 600px;
    --rotate-x: 0deg;
    --rotate-y: 0deg;
    --rotate-z: 0deg;
    display: block;
    position: relative;
    width: var(--width);
    height: var(--height);
    margin-left: auto;
    margin-right: auto;
    tbody{
        perspective: .3;
        border: solid;
        position: absolute;
        display: block;
        top: 0;
        left: 0;
        width: var(--width);
        height: var(--height);
        --dx: calc(var(--x-max) - var(--x-min));
        --dy: calc(var(--y-max) - var(--y-min));
        --dz: calc(var(--z-max) - var(--z-min));
        --x-origin: calc(-1 * var(--x-min) / var(--dx) * var(--width));
        --y-origin: calc(var(--y-max) / var(--dy) * var(--height));
        transform-style: preserve-3d;
    }
    tr {        
        top: 0;
        left: 0;
        position: absolute;
        display: inline-block;
        transform-origin: var(--x-origin) var(--y-origin) 0;
        transform: 
            translateX(var(--x-origin)) translateY(var(--y-origin))
            rotateZ(calc(360deg - var(--rotate-z))) 
            rotateY(calc(360deg - var(--rotate-y)))  
            rotateX(calc(360deg - var(--rotate-x)))
            translateX(calc(var(--x) / var(--dx) * var(--width)))
            translateY(calc(-1 * var(--y) / var(--dy) * var(--height)))
            translateZ(calc(var(--z) / var(--dz) * var(--depth)))
            rotateX(var(--rotate-x)) rotateY(var(--rotate-y)) rotateZ(var(--rotate-z));
        td {
            display: none;
        }
    }
    tr:hover, tr:focus-within {
         transform: 
            translateX(var(--x-origin)) translateY(var(--y-origin))
            rotateZ(calc(360deg - var(--rotate-z))) 
            rotateY(calc(360deg - var(--rotate-y)))  
            rotateX(calc(360deg - var(--rotate-x)))
            translateX(calc(var(--x) / var(--dx) * var(--width)))
            translateY(calc(-1 * var(--y) / var(--dy) * var(--height)))
            translateZ(calc(var(--z) / var(--dz) * var(--depth)))
            rotateX(var(--rotate-x)) rotateY(var(--rotate-y)) rotateZ(var(--rotate-z))
            translateZ(10000px);
        th, td:not([hidden]) {
            display: unset;
        }
    }
    tr:last-of-type {
        background-color: red;
    }
}
</style>
...;