Skip to content

a graph from a table¤

we'll use the extended chartability principles to experiment with a small graph.s

import networkx
from nbconvert_a11y.repr import get_table, TableOptions
shell.tangle.parser.fence_methods["graphviz"] = "graphviz:Source"
%%
create a graph in graphviz

    graph =\
```graphviz
graph LR {
    Compromising -- {Robust Understandable}
    Assistive -- {Perceivable Understandable}
    Flexible -- {Perceivable Operable Robust}
    Robust -- Perceivable -- Operable -- Robust [style=dashed]
    Perceivable -- Understandable -- Robust -- Perceivable [style=dashed]
}
```

    pour, caf = map(str.split, (
Percievable Operable Understandable Robust
Compromising Assistive Flexible

    ).splitlines())

then cast it to a networkx graph object 

    G = networkx.nx_pydot.read_dot(io.StringIO(graph.source))

create a graph in graphviz

graph =\
graph LR {
    Compromising -- {Robust Understandable}
    Assistive -- {Perceivable Understandable}
    Flexible -- {Perceivable Operable Robust}
    Robust -- Perceivable -- Operable -- Robust [style=dashed]
    Perceivable -- Understandable -- Robust -- Perceivable [style=dashed]
}
pour, caf = map(str.split, (

Percievable Operable Understandable Robust Compromising Assistive Flexible

).splitlines())

then cast it to a networkx graph object

G = networkx.nx_pydot.read_dot(io.StringIO(graph.source))

nodes are indexes that have meaning in space. there are multiple layouts

the positions would be hidden visually and non visually

chartability = await Series(["https://raw.githubusercontent.com/Chartability/POUR-CAF/main/README.md"]).http.get()
from nbconvert_a11y.table import new
%%
    rules = chartability.apply(shell.tangle.parser.parser.render).apply(bs4.BeautifulSoup, features="lxml")[0]

    hs = "h1,h2,h3,h4,h5,h6"
    headings = collections.defaultdict(list)
    for node in rules.select(F"{hs}, :is({hs}) ~ :not({hs})"):
        if node.name in hs.split(","):
            key = node
        else:
            headings[key].append(node)

    headings = Series(headings).to_frame("body")
    headings["level"] = headings.index.map(lambda x: int(x.name.lstrip("h"))).values

    headings = headings.reset_index().set_index(headings.index.attrgetter("string").rename("Heading"))
    headings["body"] = headings["body"].apply(lambda x: new("section", *x)).apply(lambda x: new("section", *x))
    headings["description"] = headings["body"].attrgetter("string")
rules = chartability.apply(shell.tangle.parser.parser.render).apply(bs4.BeautifulSoup, features="lxml")[0]

hs = "h1,h2,h3,h4,h5,h6"
headings = collections.defaultdict(list)
for node in rules.select(F"{hs}, :is({hs}) ~ :not({hs})"):
    if node.name in hs.split(","):
        key = node
    else:
        headings[key].append(node)

headings = Series(headings).to_frame("body")
headings["level"] = headings.index.map(lambda x: int(x.name.lstrip("h"))).values

headings = headings.reset_index().set_index(headings.index.attrgetter("string").rename("Heading"))
headings["body"] = headings["body"].apply(lambda x: new("section", *x)).apply(lambda x: new("section", *x))
headings["description"] = headings["body"].attrgetter("string")
%%
compute different layouts for our graph

    positions = Series({
        networkx.arf_layout: networkx.arf_layout(G),
        # networkx.bipartite_layout: networkx.bipartite_layout(G, G.nodes),
        networkx.circular_layout: networkx.circular_layout(G),
        networkx.spring_layout: networkx.spring_layout(G),
        networkx.kamada_kawai_layout: networkx.kamada_kawai_layout(G),
        networkx.planar_layout: networkx.planar_layout(G),
        networkx.shell_layout: networkx.shell_layout(G),
        networkx.spectral_layout: networkx.spectral_layout(G),
        networkx.spiral_layout: networkx.spiral_layout(G),
        networkx.spring_layout: networkx.spring_layout(G),
    }).series().T

    positions.columns = positions.columns.attrgetter("__name__").str.split("_").itemgetter(0)
    positions = positions.stack().series().rename(columns=dict(zip(range(2), "xy"))).unstack()

    positions = pandas.concat(dict(info=headings.loc[positions.index]), axis=1).join(
        positions
    ).drop(columns=[("info", "body"), ("info", "index"), ("info", "level")])

    positions["info", "heuristic"] = positions.index.map(set(pour).__contains__).map(
        ["wcag", "chartability"].__getitem__
    ).values

compute different layouts for our graph

positions = Series({
    networkx.arf_layout: networkx.arf_layout(G),
    # networkx.bipartite_layout: networkx.bipartite_layout(G, G.nodes),
    networkx.circular_layout: networkx.circular_layout(G),
    networkx.spring_layout: networkx.spring_layout(G),
    networkx.kamada_kawai_layout: networkx.kamada_kawai_layout(G),
    networkx.planar_layout: networkx.planar_layout(G),
    networkx.shell_layout: networkx.shell_layout(G),
    networkx.spectral_layout: networkx.spectral_layout(G),
    networkx.spiral_layout: networkx.spiral_layout(G),
    networkx.spring_layout: networkx.spring_layout(G),
}).series().T

positions.columns = positions.columns.attrgetter("__name__").str.split("_").itemgetter(0)
positions = positions.stack().series().rename(columns=dict(zip(range(2), "xy"))).unstack()

positions = pandas.concat(dict(info=headings.loc[positions.index]), axis=1).join(
    positions
).drop(columns=[("info", "body"), ("info", "index"), ("info", "level")])

positions["info", "heuristic"] = positions.index.map(set(pour).__contains__).map(
    ["wcag", "chartability"].__getitem__
).values
%%
make a dataframe of the information required to present the graph nodes and edges

    df = DataFrame(index=DataFrame(G.edges).drop(columns=2).drop_duplicates().set_index([0, 1]).index.rename(list("xy")))

    df = pandas.concat([
        positions.loc[df.index.get_level_values(0)].rename(columns={"x": "x0", "y": "y0"}, level=0).drop(("info",), axis=1).reset_index(drop=True),
        positions.loc[df.index.get_level_values(1)].rename(columns={"x": "x1", "y": "y1"}, level=0).drop(("info",), axis=1).reset_index(drop=True)
    ], axis=1).set_index(df.index)
ca885898-60e2-11ef-80ad-18cc18dc77e7.py:6: PerformanceWarning: indexing past lexsort depth may impact performance.
ca885898-60e2-11ef-80ad-18cc18dc77e7.py:7: PerformanceWarning: indexing past lexsort depth may impact performance.

make a dataframe of the information required to present the graph nodes and edges

df = DataFrame(index=DataFrame(G.edges).drop(columns=2).drop_duplicates().set_index([0, 1]).index.rename(list("xy")))

df = pandas.concat([
    positions.loc[df.index.get_level_values(0)].rename(columns={"x": "x0", "y": "y0"}, level=0).drop(("info",), axis=1).reset_index(drop=True),
    positions.loc[df.index.get_level_values(1)].rename(columns={"x": "x1", "y": "y1"}, level=0).drop(("info",), axis=1).reset_index(drop=True)
], axis=1).set_index(df.index)
%%
show the whole thing

    section = df.pipe(get_table, id="edges", options=TableOptions(max_columns=1000))
    section.table.caption.clear()
    section.table.insert(0, new("caption", F"a graph with {G.number_of_nodes()} nodes and {G.number_of_edges()} edges."))
    section.table.attrs["class"] = "visual"
    layouts = new(
        "fieldset",
        new("legend","layouts"),
        new("label", new("input", type="checkbox", checked="", name="active", onchange=
```javascript
document.getElementById(`edges`).classList.toggle("visual", this.checked);
                         console.log(this)
```
                        ), "active"),
        *(
            new("label",
                new("input", type="radio", name="layout", **{"aria-controls": F"nodes-layout-{layout}"}, onchange=
```javascript
document.querySelectorAll(`[name=layout]`).forEach((element) => {
    var target = document.getElementById(element.getAttribute(`aria-controls`));
    target.setAttribute(`media`, element.checked ? `all` : `none`);
})
```
               ), layout) for layout in positions["x"].columns
        )   
    )
    layouts.select_one("[name=layout]").attrs["checked"] = ""
    section.form.append(layouts)
    for i, layout in enumerate(positions["x"].columns):
        section.append(new("style",
```css
#edges.visual {
    tr{
        --x0: var(--x0-%s);
        --x0-min: var(--x0-%s-min);
        --x0-max: var(--x0-%s-max);
        --x0-diff: var(--x0-%s-diff);
        --x1: var(--x1-%s);
        --x1-min: var(--x0-%s-min);
        --x1-max: var(--x0-%s-max);
        --x1-diff: var(--x0-%s-diff);
        --y0: var(--y0-%s);
        --y0-min: var(--y0-%s-min);
        --y0-max: var(--y0-%s-max);
        --y0-diff: var(--y0-%s-diff);
        --y1: var(--y1-%s);
        --y1-min: var(--y0-%s-min);
        --y1-max: var(--y0-%s-max);
        --y1-diff: var(--y0-%s-diff);
    }
}
```
        .replace("%s", layout), id=F"nodes-layout-{layout}", media=i and "none" or "all"))

    section.form.append(layouts)
    HTML(section)
a graph with 7 nodes and 13 edges.
xyx0x0x0x0x0x0x0x0y0y0y0y0y0y0y0y0x1x1x1x1x1x1x1x1y1y1y1y1y1y1y1y1
arfcircularspringkamadaplanarshellspectralspiralarfcircularspringkamadaplanarshellspectralspiralarfcircularspringkamadaplanarshellspectralspiralarfcircularspringkamadaplanarshellspectralspiral
CompromisingRobust0.4581.000-0.8920.907-1.000-1.0000.647-0.033-0.0790.0000.6270.759-0.429-0.000-1.000-0.834-0.4430.6230.0400.440-0.800-0.623-0.1470.2672.2010.7820.321-0.098-0.229-0.782-0.162-0.724
CompromisingUnderstandable0.4581.000-0.8920.907-1.000-1.0000.647-0.033-0.0790.0000.6270.759-0.429-0.000-1.000-0.8340.924-0.223-0.634-0.0511.0000.2230.4140.4551.4750.975-0.1780.754-0.429-0.975-0.000-0.422
RobustFlexible-0.4430.6230.0400.440-0.800-0.623-0.1470.2672.2010.7820.321-0.098-0.229-0.782-0.162-0.7242.198-0.2230.791-0.3110.0000.223-0.708-0.3170.022-0.9750.530-0.9700.5710.9750.0000.737
RobustPerceivable-0.4430.6230.0400.440-0.800-0.623-0.1470.2672.2010.7820.321-0.098-0.229-0.782-0.162-0.724-1.088-0.9010.196-0.4230.4000.901-0.1470.1840.626-0.434-0.256-0.1560.1710.4340.1620.425
RobustOperable-0.4430.6230.0400.440-0.800-0.623-0.1470.2672.2010.7820.321-0.098-0.229-0.782-0.162-0.7240.2750.6230.9470.4380.000-0.623-0.708-1.000-1.107-0.782-0.044-0.9200.3710.7820.0000.820
RobustUnderstandable-0.4430.6230.0400.440-0.800-0.623-0.1470.2672.2010.7820.321-0.098-0.229-0.782-0.162-0.7240.924-0.223-0.634-0.0511.0000.2230.4140.4551.4750.975-0.1780.754-0.429-0.975-0.000-0.422
UnderstandableAssistive0.924-0.223-0.634-0.0511.0000.2230.4140.4551.4750.975-0.1780.754-0.429-0.975-0.000-0.4221.956-0.901-0.448-1.0000.4000.9010.6470.4442.1340.434-1.0000.631-0.029-0.4341.000-0.003
UnderstandablePerceivable0.924-0.223-0.634-0.0511.0000.2230.4140.4551.4750.975-0.1780.754-0.429-0.975-0.000-0.422-1.088-0.9010.196-0.4230.4000.901-0.1470.1840.626-0.434-0.256-0.1560.1710.4340.1620.425
AssistivePerceivable1.956-0.901-0.448-1.0000.4000.9010.6470.4442.1340.434-1.0000.631-0.029-0.4341.000-0.003-1.088-0.9010.196-0.4230.4000.901-0.1470.1840.626-0.434-0.256-0.1560.1710.4340.1620.425
PerceivableFlexible-1.088-0.9010.196-0.4230.4000.901-0.1470.1840.626-0.434-0.256-0.1560.1710.4340.1620.4252.198-0.2230.791-0.3110.0000.223-0.708-0.3170.022-0.9750.530-0.9700.5710.9750.0000.737
PerceivableOperable-1.088-0.9010.196-0.4230.4000.901-0.1470.1840.626-0.434-0.256-0.1560.1710.4340.1620.4250.2750.6230.9470.4380.000-0.623-0.708-1.000-1.107-0.782-0.044-0.9200.3710.7820.0000.820
FlexibleOperable2.198-0.2230.791-0.3110.0000.223-0.708-0.3170.022-0.9750.530-0.9700.5710.9750.0000.7370.2750.6230.9470.4380.000-0.623-0.708-1.000-1.107-0.782-0.044-0.9200.3710.7820.0000.820
min-1.088-0.901-0.892-1.000-1.000-1.000-0.708-0.317-0.079-0.975-1.000-0.970-0.429-0.975-1.000-0.834-1.088-0.901-0.634-1.000-0.800-0.623-0.708-1.000-1.107-0.975-1.000-0.970-0.429-0.975-0.162-0.724
max2.1981.0000.7910.9071.0000.9010.6470.4552.2010.9750.6270.7590.5710.9751.0000.7372.1980.6230.9470.4401.0000.9010.6470.4552.2010.9750.5300.7540.5710.9751.0000.820
diff3.2861.9011.6831.9072.0001.9011.3540.7732.2811.9501.6271.7291.0001.9502.0001.5713.2861.5241.5811.4401.8001.5241.3541.4553.3081.9501.5301.7241.0001.9501.1621.544

layouts

show the whole thing

section = df.pipe(get_table, id="edges", options=TableOptions(max_columns=1000))
section.table.caption.clear()
section.table.insert(0, new("caption", F"a graph with {G.number_of_nodes()} nodes and {G.number_of_edges()} edges."))
section.table.attrs["class"] = "visual"
layouts = new(
    "fieldset",
    new("legend","layouts"),
    new("label", new("input", type="checkbox", checked="", name="active", onchange=
document.getElementById(`edges`).classList.toggle("visual", this.checked);
                         console.log(this)
                    ), "active"),
    *(
        new("label",
            new("input", type="radio", name="layout", **{"aria-controls": F"nodes-layout-{layout}"}, onchange=
document.querySelectorAll(`[name=layout]`).forEach((element) => {
    var target = document.getElementById(element.getAttribute(`aria-controls`));
    target.setAttribute(`media`, element.checked ? `all` : `none`);
})
           ), layout) for layout in positions["x"].columns
    )   
)
layouts.select_one("[name=layout]").attrs["checked"] = ""
section.form.append(layouts)
for i, layout in enumerate(positions["x"].columns):
    section.append(new("style",
#edges.visual {
    tr{
        --x0: var(--x0-%s);
        --x0-min: var(--x0-%s-min);
        --x0-max: var(--x0-%s-max);
        --x0-diff: var(--x0-%s-diff);
        --x1: var(--x1-%s);
        --x1-min: var(--x0-%s-min);
        --x1-max: var(--x0-%s-max);
        --x1-diff: var(--x0-%s-diff);
        --y0: var(--y0-%s);
        --y0-min: var(--y0-%s-min);
        --y0-max: var(--y0-%s-max);
        --y0-diff: var(--y0-%s-diff);
        --y1: var(--y1-%s);
        --y1-min: var(--y0-%s-min);
        --y1-max: var(--y0-%s-max);
        --y1-diff: var(--y0-%s-diff);
    }
}
    .replace("%s", layout), id=F"nodes-layout-{layout}", media=i and "none" or "all"))

section.form.append(layouts)
HTML(section)
%%
visual styling for the graph

```css
#edges.visual {
    --w: 400;
    --h: 400;
    width: var(--w); height: calc(200px + var(--h) * 1px);
    display: block;
    padding: 100px;
    tbody {
        position: relative;
        display: inline-block;
        width: var(--w); height: var(--h);
        tr {
            display: grid;
            grid-template-columns: 1fr;
            grid-template-rows: 1fr;
            position: absolute;
            --X0: calc((var(--x0) - var(--x0-min)) / var(--x0-diff) * var(--w));
            --Y0: calc((var(--y0) - var(--y0-min)) / var(--y0-diff) * var(--h));
            --X1: calc((var(--x1) - var(--x0-min)) / var(--x0-diff) * var(--w));
            --Y1: calc((var(--y1) - var(--y0-min)) / var(--y0-diff) * var(--h));
            --d: calc(
                pow(pow(var(--X1) - var(--X0), 2) + pow(var(--Y1) - var(--Y0), 2), .5)
            );
            --r: calc(atan2(var(--Y1) - var(--Y0), var(--X1) - var(--X0)));
            th {
                position: absolute;
                display: inline-block;
                transition: 2s all;
                &:nth-of-type(1) {
                    transform:
                        translate(
                            calc(var(--X0) * 1px), calc(var(--Y0) * 1px)
                        )
                        translate(-50%, -50%)
                        ;
                }
                &:nth-of-type(2) {
                    transform:
                        translate(
                            calc(var(--X1) * 1px), calc(var(--Y1) * 1px)
                        )
                        translate(-50%, -50%)

                        ;
                }
            }  
            td {
                display: none;
            }
            &::before {
                        content: "";
                        display: content;
                        position: absolute;
                        transform-origin: calc(1px *var(--X0)) calc(1px *var(--Y0));
                        height: 1px;
                        border: solid 2px;
                        transition: 2s all;
                        width: calc(var(--d) * 1px);
                        transform: 
                            rotate(var(--r))
                            translate(
                                calc(var(--X0) * 1px), calc(var(--Y0) * 1px)
                            )
                            ;
                    }
        }

    }
```

visual styling for the graph

#edges.visual {
    --w: 400;
    --h: 400;
    width: var(--w); height: calc(200px + var(--h) * 1px);
    display: block;
    padding: 100px;
    tbody {
        position: relative;
        display: inline-block;
        width: var(--w); height: var(--h);
        tr {
            display: grid;
            grid-template-columns: 1fr;
            grid-template-rows: 1fr;
            position: absolute;
            --X0: calc((var(--x0) - var(--x0-min)) / var(--x0-diff) * var(--w));
            --Y0: calc((var(--y0) - var(--y0-min)) / var(--y0-diff) * var(--h));
            --X1: calc((var(--x1) - var(--x0-min)) / var(--x0-diff) * var(--w));
            --Y1: calc((var(--y1) - var(--y0-min)) / var(--y0-diff) * var(--h));
            --d: calc(
                pow(pow(var(--X1) - var(--X0), 2) + pow(var(--Y1) - var(--Y0), 2), .5)
            );
            --r: calc(atan2(var(--Y1) - var(--Y0), var(--X1) - var(--X0)));
            th {
                position: absolute;
                display: inline-block;
                transition: 2s all;
                &:nth-of-type(1) {
                    transform:
                        translate(
                            calc(var(--X0) * 1px), calc(var(--Y0) * 1px)
                        )
                        translate(-50%, -50%)
                        ;
                }
                &:nth-of-type(2) {
                    transform:
                        translate(
                            calc(var(--X1) * 1px), calc(var(--Y1) * 1px)
                        )
                        translate(-50%, -50%)
                        
                        ;
                }
            }  
            td {
                display: none;
            }
            &::before {
                        content: "";
                        display: content;
                        position: absolute;
                        transform-origin: calc(1px *var(--X0)) calc(1px *var(--Y0));
                        height: 1px;
                        border: solid 2px;
                        transition: 2s all;
                        width: calc(var(--d) * 1px);
                        transform: 
                            rotate(var(--r))
                            translate(
                                calc(var(--X0) * 1px), calc(var(--Y0) * 1px)
                            )
                            ;
                    }
        }
        
    }

marginalia¤

what describes the figure and form of a graph?

the graph form has many typographical layouts that new to be considered

there are two tables, two maps, a legend of nodes, and an index of relative positions.

nodes and edges

nodes are primary foreground forms edges are second mid ground forms all other attributes are background