chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 12:25:07 +08:00
commit a26e856398
1681 changed files with 296950 additions and 0 deletions
@@ -0,0 +1,197 @@
digraph G {
bgcolor=transparent
graph [rankdir="LR" fontname="helvetica" labeljust="l"]
node [shape="box" fontname="monospace" fontsize=8 color=gray70 style=filled fillcolor=white];
edge [color="#EDEBDF" arrowsize=0.8]
subgraph cluster_11 {
label="\lPython Server";
fontcolor=gray30
margin=10
color=none
subgraph cluster_thread_2 {
graph [
label="\lPerspectiveManager - Thread 2";
style=filled
fillcolor="#91A4A8"
color=none
fontcolor="#EDEBDF"
fontsize=10
margin=10
]
table_thread_2 [
label="table(df)"
width=1
color=none
fillcolor="#E6E2DA"
]
view_thread_2 [
label="view({\l group_by: [\"State\"]]\l})\l"
width=2.5
color=none
fillcolor="#EDEBDF"
]
view_thread_2_2 [
label="view({\l group_by: [\"City\"]]\l})\l"
width=2.5
color=none
fillcolor="#EDEBDF"
]
table_thread_2 -> view_thread_2_2;
table_thread_2 -> view_thread_2;
}
subgraph cluster_thread_1 {
graph [
label="\lPerspectiveManager - Thread 1";
style=filled
fillcolor="#91A4A8"
color=none
fontcolor="#EDEBDF"
fontsize=10
margin=10
]
table_thread_1 [
label="table(arrow)"
width=1
color=none
fillcolor="#E6E2DA"
]
view_thread_1 [
label="view({\l group_by:'[\"State\"]\l split_by:'[\"Segment\"]'\l})\l"
width=2.5
color=none
fillcolor="#EDEBDF"
]
table_thread_1 -> view_thread_1;
}
}
subgraph cluster_browser {
graph [
label="\lBrowser";
color="#CADEE1";
margin=10
style=filled;
fontcolor=gray30
]
subgraph cluster_2 {
graph [
label="\lWebWorker 1";
style=filled
margin=10
fillcolor="#2D4C68"
color=none
fontcolor="#EDEBDF"
fontsize=10
]
table1 [
label="table(csv)"
width=1
color=none
fillcolor="#E6E2DA"
]
table_remote_view [
label="table(json)"
width=1
color=none
fillcolor="#E6E2DA"
]
view1 [
label="view({\l group_by: [\"Category\"]\l filter: [[\"State\",\"==\",\"Texas\"]]\l})\l"
width=2.5
color=none
fillcolor="#EDEBDF"
]
view2 [
label="view({\l group_by: [\"Sub-Category\"]\l})\l"
width=2.5
color=none
fillcolor="#EDEBDF"
]
view3 [
label="view()\l"
width=2.5
color=none
fillcolor="#EDEBDF"
]
table1 -> {view1 view2};
table_remote_view -> view3;
}
subgraph cluster_webworker2 {
graph [
label="\lWebWorker 2";
style=filled
margin=10
fillcolor="#2D4C68"
color=none
fontcolor="#EDEBDF"
fontsize=10
]
table12 [
label="table(...)"
width=1
color=none
fillcolor="#E6E2DA"
]
view12 [
label="view({\l group_by: [\"Category\"]\l filter: [[\"State\",\"==\",\"Texas\"]]\l})\l"
width=2.5
color=none
fillcolor="#EDEBDF"
]
table12 -> {view12} [color="#E6E2DA"];
}
view_thread_2 -> table12 [penwidth=2 style=dashed arrowhead=none color="#D1A043"];
view1 -> viewer1 [penwidth=2 style=dashed arrowhead=none color="#666"];
view2 -> viewer2 [penwidth=2 style=dashed arrowhead=none color="#666"];
view3 -> viewer3 [penwidth=2 style=dashed arrowhead=none color="#666"];
subgraph cluster_41 {
graph [
label="\l<html>";
color=none
fillcolor=white
fontcolor=gray30
fontsize=10
fontname="monospace" fontsize=8 color=none
]
viewer1 [
label = "<perspective-viewer\l view=\"Y Bar\"\l row-pivots='[\"Category\"]'\l filters='[[\"State\",\"==\",\"Texas\"]]'>\l"
width=2.8
];
viewer2 [
label = "<perspective-viewer\l view=\"xy_scatter\"\l row-pivots='[\"Sub-Category\"]'>\l"
width=2.8
color=lightgrey
];
viewer3 [
label = "<perspective-viewer\l view=\"grid\">\l"
width=2.8
];
viewerN [
style=invis
]
viewer5 [
label = "<perspective-viewer\l view=\"heatmap\"\l row-pivots='[\"State\"]\l column-pivots='[\"Segment\"]'>\l"
width=2.8
];
viewerZ [
style=invis
height=0.3
]
viewer4 [
label = "<perspective-viewer\l view=\"heatmap\"\l row-pivots='[\"State\"]\l column-pivots='[\"Segment\"]'>\l"
width=2.8
];
view_thread_1 -> viewer4 [penwidth=2 style=dashed arrowhead=none color="#D1A043" constraint=false];
view12 -> viewer5 [penwidth=2 style=dashed arrowhead=none color="#666"];
}
}
}
@@ -0,0 +1,66 @@
digraph G {
bgcolor=transparent
graph [rankdir="LR" fontname="helvetica" labeljust="l"]
node [shape="box" fontname="monospace" fontsize=8 color=gray70 style=filled fillcolor=white];
edge [color="#EDEBDF" arrowsize=0.8]
subgraph cluster_browser {
graph [
label="\lBrowser";
color="#CADEE1";
margin=10
style=filled;
fontcolor=gray30
]
subgraph cluster_2 {
graph [
label="\lWebWorker 1";
style=filled
margin=10
fillcolor="#2D4C68"
color=none
fontcolor="#EDEBDF"
fontsize=10
]
table1 [
label="table(csv)"
width=1
color=none
fillcolor="#E6E2DA"
]
view2 [
label="view({\l group_by: [\"Sub-Category\"]\l})\l"
width=2.5
color=none
fillcolor="#EDEBDF"
]
table1 -> {view2};
}
view2 -> viewer2 [penwidth=2 style=dashed arrowhead=none color="#666"];
subgraph cluster_41 {
graph [
label="\l<html>";
color=none
fillcolor=white
fontcolor=gray30
fontsize=10
fontname="monospace" fontsize=8 color=none
]
viewer2 [
label = "<perspective-viewer\l view=\"xy_scatter\"\l row-pivots='[\"Sub-Category\"]'>\l"
width=2.8
color=lightgrey
];
}
}
}
@@ -0,0 +1,60 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Generated by graphviz version 5.0.0 (20220707.1540)
-->
<!-- Title: G Pages: 1 -->
<svg width="590pt" height="170pt"
viewBox="0.00 0.00 590.00 170.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 166)">
<title>G</title>
<g id="clust1" class="cluster">
<title>cluster_browser</title>
<polygon fill="#cadee1" stroke="#cadee1" points="8,-8 8,-154 574,-154 574,-8 8,-8"/>
<text text-anchor="middle" x="41" y="-122.8" font-family="Helvetica,sans-Serif" font-size="14.00" fill="#4d4d4d">Browser</text>
</g>
<g id="clust2" class="cluster">
<title>cluster_2</title>
<polygon fill="#2d4c68" stroke="transparent" points="18,-18 18,-105 326,-105 326,-18 18,-18"/>
<text text-anchor="middle" x="55.5" y="-81" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#edebdf">WebWorker 1</text>
</g>
<g id="clust4" class="cluster">
<title>cluster_41</title>
<polygon fill="white" stroke="transparent" points="342,-18 342,-100 564,-100 564,-18 342,-18"/>
<text text-anchor="middle" x="364" y="-80.6" font-family="monospace" font-size="8.00" fill="#4d4d4d">&lt;html&gt;</text>
</g>
<!-- table1 -->
<g id="node1" class="node">
<title>table1</title>
<polygon fill="#e6e2da" stroke="transparent" points="100,-64 28,-64 28,-28 100,-28 100,-64"/>
<text text-anchor="middle" x="64" y="-44.1" font-family="monospace" font-size="8.00">table(csv)</text>
</g>
<!-- view2 -->
<g id="node2" class="node">
<title>view2</title>
<polygon fill="#edebdf" stroke="transparent" points="316,-64 136,-64 136,-28 316,-28 316,-64"/>
<text text-anchor="start" x="144" y="-53.1" font-family="monospace" font-size="8.00">view({</text>
<text text-anchor="start" x="144" y="-44.1" font-family="monospace" font-size="8.00"> &#160;&#160;&#160;group_by: [&quot;Sub&#45;Category&quot;]</text>
<text text-anchor="start" x="144" y="-35.1" font-family="monospace" font-size="8.00">})</text>
</g>
<!-- table1&#45;&gt;view2 -->
<g id="edge1" class="edge">
<title>table1&#45;&gt;view2</title>
<path fill="none" stroke="#edebdf" d="M100.11,-46C108.52,-46 117.91,-46 127.66,-46"/>
<polygon fill="#edebdf" stroke="#edebdf" points="127.91,-48.8 135.91,-46 127.91,-43.2 127.91,-48.8"/>
</g>
<!-- viewer2 -->
<g id="node3" class="node">
<title>viewer2</title>
<polygon fill="white" stroke="lightgrey" points="554,-64 352,-64 352,-28 554,-28 554,-64"/>
<text text-anchor="start" x="360" y="-53.1" font-family="monospace" font-size="8.00">&lt;perspective&#45;viewer</text>
<text text-anchor="start" x="360" y="-44.1" font-family="monospace" font-size="8.00"> &#160;&#160;&#160;view=&quot;xy_scatter&quot;</text>
<text text-anchor="start" x="360" y="-35.1" font-family="monospace" font-size="8.00"> &#160;&#160;&#160;row&#45;pivots=&#39;[&quot;Sub&#45;Category&quot;]&#39;&gt;</text>
</g>
<!-- view2&#45;&gt;viewer2 -->
<g id="edge2" class="edge">
<title>view2&#45;&gt;viewer2</title>
<path fill="none" stroke="#000000" stroke-width="2" stroke-dasharray="5,2" d="M316.25,-46C327.9,-46 339.91,-46 351.73,-46"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.2 KiB

@@ -0,0 +1,66 @@
digraph G {
bgcolor=transparent
graph [rankdir="LR" fontname="helvetica" labeljust="l"]
node [shape="box" fontname="monospace" fontsize=8 color=gray70 style=filled fillcolor=white];
edge [color="#EDEBDF" arrowsize=0.8]
subgraph cluster_11 {
label="\lPython Server";
fontcolor=gray30
margin=10
color=none
subgraph cluster_thread_1 {
graph [
label="\lPerspectiveManager - Thread 1";
style=filled
fillcolor="#91A4A8"
color=none
fontcolor="#EDEBDF"
fontsize=10
margin=10
]
table_thread_1 [
label="table(arrow)"
width=1
color=none
fillcolor="#E6E2DA"
]
view_thread_1 [
label="view({\l group_by:'[\"State\"]\l split_by:'[\"Segment\"]'\l})\l"
width=2.5
color=none
fillcolor="#EDEBDF"
]
table_thread_1 -> view_thread_1;
}
}
subgraph cluster_browser {
graph [
label="\lBrowser";
color="#CADEE1";
margin=10
style=filled;
fontcolor=gray30
]
subgraph cluster_41 {
graph [
label="\l<html>";
color=none
fillcolor=white
fontcolor=gray30
fontsize=10
fontname="monospace" fontsize=8 color=none
]
viewer4 [
label = "<perspective-viewer\l view=\"heatmap\"\l row-pivots='[\"State\"]\l column-pivots='[\"Segment\"]'>\l"
width=2.8
];
view_thread_1 -> viewer4 [penwidth=2 style=dashed arrowhead=none color="#D1A043"];
}
}
}
@@ -0,0 +1,67 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Generated by graphviz version 5.0.0 (20220707.1540)
-->
<!-- Title: G Pages: 1 -->
<svg width="602pt" height="178pt"
viewBox="0.00 0.00 602.00 178.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 174)">
<title>G</title>
<g id="clust1" class="cluster">
<title>cluster_11</title>
<polygon fill="transparent" stroke="transparent" points="8,-8 8,-162 336,-162 336,-8 8,-8"/>
<text text-anchor="middle" x="59" y="-130.8" font-family="Helvetica,sans-Serif" font-size="14.00" fill="#4d4d4d">Python Server</text>
</g>
<g id="clust2" class="cluster">
<title>cluster_thread_1</title>
<polygon fill="#91a4a8" stroke="transparent" points="18,-18 18,-113 326,-113 326,-18 18,-18"/>
<text text-anchor="middle" x="94.5" y="-89" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#edebdf">PerspectiveManager &#45; Thread 1</text>
</g>
<g id="clust3" class="cluster">
<title>cluster_browser</title>
<polygon fill="#cadee1" stroke="#cadee1" points="344,-8 344,-157 586,-157 586,-8 344,-8"/>
<text text-anchor="middle" x="377" y="-125.8" font-family="Helvetica,sans-Serif" font-size="14.00" fill="#4d4d4d">Browser</text>
</g>
<g id="clust4" class="cluster">
<title>cluster_41</title>
<polygon fill="white" stroke="transparent" points="354,-18 354,-108 576,-108 576,-18 354,-18"/>
<text text-anchor="middle" x="376" y="-88.6" font-family="monospace" font-size="8.00" fill="#4d4d4d">&lt;html&gt;</text>
</g>
<!-- table_thread_1 -->
<g id="node1" class="node">
<title>table_thread_1</title>
<polygon fill="#e6e2da" stroke="transparent" points="100,-68 28,-68 28,-32 100,-32 100,-68"/>
<text text-anchor="middle" x="64" y="-48.1" font-family="monospace" font-size="8.00">table(arrow)</text>
</g>
<!-- view_thread_1 -->
<g id="node2" class="node">
<title>view_thread_1</title>
<polygon fill="#edebdf" stroke="transparent" points="316,-72 136,-72 136,-28 316,-28 316,-72"/>
<text text-anchor="start" x="144" y="-61.6" font-family="monospace" font-size="8.00">view({</text>
<text text-anchor="start" x="144" y="-52.6" font-family="monospace" font-size="8.00"> &#160;&#160;&#160;group_by:&#39;[&quot;State&quot;]</text>
<text text-anchor="start" x="144" y="-43.6" font-family="monospace" font-size="8.00"> &#160;&#160;&#160;split_by:&#39;[&quot;Segment&quot;]&#39;</text>
<text text-anchor="start" x="144" y="-34.6" font-family="monospace" font-size="8.00">})</text>
</g>
<!-- table_thread_1&#45;&gt;view_thread_1 -->
<g id="edge1" class="edge">
<title>table_thread_1&#45;&gt;view_thread_1</title>
<path fill="none" stroke="#edebdf" d="M100.11,-50C108.52,-50 117.91,-50 127.66,-50"/>
<polygon fill="#edebdf" stroke="#edebdf" points="127.91,-52.8 135.91,-50 127.91,-47.2 127.91,-52.8"/>
</g>
<!-- viewer4 -->
<g id="node3" class="node">
<title>viewer4</title>
<polygon fill="white" stroke="#b3b3b3" points="566,-72 364,-72 364,-28 566,-28 566,-72"/>
<text text-anchor="start" x="372" y="-61.6" font-family="monospace" font-size="8.00">&lt;perspective&#45;viewer</text>
<text text-anchor="start" x="372" y="-52.6" font-family="monospace" font-size="8.00"> &#160;&#160;&#160;view=&quot;heatmap&quot;</text>
<text text-anchor="start" x="372" y="-43.6" font-family="monospace" font-size="8.00"> &#160;&#160;&#160;row&#45;pivots=&#39;[&quot;State&quot;]</text>
<text text-anchor="start" x="372" y="-34.6" font-family="monospace" font-size="8.00"> &#160;&#160;&#160;column&#45;pivots=&#39;[&quot;Segment&quot;]&#39;&gt;</text>
</g>
<!-- view_thread_1&#45;&gt;viewer4 -->
<g id="edge2" class="edge">
<title>view_thread_1&#45;&gt;viewer4</title>
<path fill="none" stroke="#d1a043" stroke-width="2" stroke-dasharray="5,2" d="M316.26,-50C331.75,-50 347.94,-50 363.69,-50"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.8 KiB

@@ -0,0 +1,97 @@
digraph G {
bgcolor=transparent
graph [rankdir="LR" fontname="helvetica" labeljust="l"]
node [shape="box" fontname="monospace" fontsize=8 color=gray70 style=filled fillcolor=white];
edge [color="#EDEBDF" arrowsize=0.8]
subgraph cluster_11 {
label="\lPython Server";
fontcolor=gray30
margin=10
color=none
subgraph cluster_thread_2 {
graph [
label="\lPerspectiveManager - Thread 2";
style=filled
fillcolor="#91A4A8"
color=none
fontcolor="#EDEBDF"
fontsize=10
margin=10
]
table_thread_2 [
label="table(df)"
width=1
color=none
fillcolor="#E6E2DA"
]
view_thread_2 [
label="view({\l group_by: [\"State\"]]\l})\l"
width=2.5
color=none
fillcolor="#EDEBDF"
]
table_thread_2 -> view_thread_2;
}
}
subgraph cluster_browser {
graph [
label="\lBrowser";
color="#CADEE1";
margin=10
style=filled;
fontcolor=gray30
]
subgraph cluster_webworker2 {
graph [
label="\lWebWorker 2";
style=filled
margin=10
fillcolor="#2D4C68"
color=none
fontcolor="#EDEBDF"
fontsize=10
]
table12 [
label="table(...)"
width=1
color=none
fillcolor="#E6E2DA"
]
view12 [
label="view({\l group_by: [\"Category\"]\l filter: [[\"State\",\"==\",\"Texas\"]]\l})\l"
width=2.5
color=none
fillcolor="#EDEBDF"
]
table12 -> {view12} [color="#E6E2DA"];
}
view_thread_2 -> table12 [penwidth=2 style=dashed arrowhead=none color="#D1A043"];
subgraph cluster_41 {
graph [
label="\l<html>";
color=none
fillcolor=white
fontcolor=gray30
fontsize=10
fontname="monospace" fontsize=8 color=none
]
viewer5 [
label = "<perspective-viewer\l view=\"heatmap\"\l row-pivots='[\"State\"]\l column-pivots='[\"Segment\"]'>\l"
width=2.8
];
view12 -> viewer5 [penwidth=2 style=dashed arrowhead=none color="#666"];
}
}
}
@@ -0,0 +1,97 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Generated by graphviz version 5.0.0 (20220707.1540)
-->
<!-- Title: G Pages: 1 -->
<svg width="926pt" height="178pt"
viewBox="0.00 0.00 926.00 178.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 174)">
<title>G</title>
<g id="clust1" class="cluster">
<title>cluster_11</title>
<polygon fill="transparent" stroke="transparent" points="8,-12 8,-158 336,-158 336,-12 8,-12"/>
<text text-anchor="middle" x="59" y="-126.8" font-family="Helvetica,sans-Serif" font-size="14.00" fill="#4d4d4d">Python Server</text>
</g>
<g id="clust2" class="cluster">
<title>cluster_thread_2</title>
<polygon fill="#91a4a8" stroke="transparent" points="18,-22 18,-109 326,-109 326,-22 18,-22"/>
<text text-anchor="middle" x="94.5" y="-85" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#edebdf">PerspectiveManager &#45; Thread 2</text>
</g>
<g id="clust3" class="cluster">
<title>cluster_browser</title>
<polygon fill="#cadee1" stroke="#cadee1" points="344,-8 344,-162 910,-162 910,-8 344,-8"/>
<text text-anchor="middle" x="377" y="-130.8" font-family="Helvetica,sans-Serif" font-size="14.00" fill="#4d4d4d">Browser</text>
</g>
<g id="clust4" class="cluster">
<title>cluster_webworker2</title>
<polygon fill="#2d4c68" stroke="transparent" points="354,-18 354,-113 662,-113 662,-18 354,-18"/>
<text text-anchor="middle" x="391.5" y="-89" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#edebdf">WebWorker 2</text>
</g>
<g id="clust6" class="cluster">
<title>cluster_41</title>
<polygon fill="white" stroke="transparent" points="678,-18 678,-108 900,-108 900,-18 678,-18"/>
<text text-anchor="middle" x="700" y="-88.6" font-family="monospace" font-size="8.00" fill="#4d4d4d">&lt;html&gt;</text>
</g>
<!-- table_thread_2 -->
<g id="node1" class="node">
<title>table_thread_2</title>
<polygon fill="#e6e2da" stroke="transparent" points="100,-68 28,-68 28,-32 100,-32 100,-68"/>
<text text-anchor="middle" x="64" y="-48.1" font-family="monospace" font-size="8.00">table(df)</text>
</g>
<!-- view_thread_2 -->
<g id="node2" class="node">
<title>view_thread_2</title>
<polygon fill="#edebdf" stroke="transparent" points="316,-68 136,-68 136,-32 316,-32 316,-68"/>
<text text-anchor="start" x="144" y="-57.1" font-family="monospace" font-size="8.00">view({</text>
<text text-anchor="start" x="144" y="-48.1" font-family="monospace" font-size="8.00"> &#160;&#160;&#160;group_by: [&quot;State&quot;]]</text>
<text text-anchor="start" x="144" y="-39.1" font-family="monospace" font-size="8.00">})</text>
</g>
<!-- table_thread_2&#45;&gt;view_thread_2 -->
<g id="edge1" class="edge">
<title>table_thread_2&#45;&gt;view_thread_2</title>
<path fill="none" stroke="#edebdf" d="M100.11,-50C108.52,-50 117.91,-50 127.66,-50"/>
<polygon fill="#edebdf" stroke="#edebdf" points="127.91,-52.8 135.91,-50 127.91,-47.2 127.91,-52.8"/>
</g>
<!-- table12 -->
<g id="node3" class="node">
<title>table12</title>
<polygon fill="#e6e2da" stroke="transparent" points="436,-68 364,-68 364,-32 436,-32 436,-68"/>
<text text-anchor="middle" x="400" y="-48.1" font-family="monospace" font-size="8.00">table(...)</text>
</g>
<!-- view_thread_2&#45;&gt;table12 -->
<g id="edge3" class="edge">
<title>view_thread_2&#45;&gt;table12</title>
<path fill="none" stroke="#d1a043" stroke-width="2" stroke-dasharray="5,2" d="M316.02,-50C332.91,-50 349.64,-50 363.61,-50"/>
</g>
<!-- view12 -->
<g id="node4" class="node">
<title>view12</title>
<polygon fill="#edebdf" stroke="transparent" points="652,-72 472,-72 472,-28 652,-28 652,-72"/>
<text text-anchor="start" x="480" y="-61.6" font-family="monospace" font-size="8.00">view({</text>
<text text-anchor="start" x="480" y="-52.6" font-family="monospace" font-size="8.00"> &#160;&#160;&#160;group_by: [&quot;Category&quot;]</text>
<text text-anchor="start" x="480" y="-43.6" font-family="monospace" font-size="8.00"> &#160;&#160;&#160;filter: [[&quot;State&quot;,&quot;==&quot;,&quot;Texas&quot;]]</text>
<text text-anchor="start" x="480" y="-34.6" font-family="monospace" font-size="8.00">})</text>
</g>
<!-- table12&#45;&gt;view12 -->
<g id="edge2" class="edge">
<title>table12&#45;&gt;view12</title>
<path fill="none" stroke="#e6e2da" d="M436.11,-50C444.52,-50 453.91,-50 463.66,-50"/>
<polygon fill="#e6e2da" stroke="#e6e2da" points="463.91,-52.8 471.91,-50 463.91,-47.2 463.91,-52.8"/>
</g>
<!-- viewer5 -->
<g id="node5" class="node">
<title>viewer5</title>
<polygon fill="white" stroke="#b3b3b3" points="890,-72 688,-72 688,-28 890,-28 890,-72"/>
<text text-anchor="start" x="696" y="-61.6" font-family="monospace" font-size="8.00">&lt;perspective&#45;viewer</text>
<text text-anchor="start" x="696" y="-52.6" font-family="monospace" font-size="8.00"> &#160;&#160;&#160;view=&quot;heatmap&quot;</text>
<text text-anchor="start" x="696" y="-43.6" font-family="monospace" font-size="8.00"> &#160;&#160;&#160;row&#45;pivots=&#39;[&quot;State&quot;]</text>
<text text-anchor="start" x="696" y="-34.6" font-family="monospace" font-size="8.00"> &#160;&#160;&#160;column&#45;pivots=&#39;[&quot;Segment&quot;]&#39;&gt;</text>
</g>
<!-- view12&#45;&gt;viewer5 -->
<g id="edge4" class="edge">
<title>view12&#45;&gt;viewer5</title>
<path fill="none" stroke="#000000" stroke-width="2" stroke-dasharray="5,2" d="M652.25,-50C663.9,-50 675.91,-50 687.73,-50"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.4 KiB

@@ -0,0 +1,250 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Generated by graphviz version 5.0.0 (20220707.1540)
-->
<!-- Title: G Pages: 1 -->
<svg width="926pt" height="499pt"
viewBox="0.00 0.00 926.00 499.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 495)">
<title>G</title>
<g id="clust1" class="cluster">
<title>cluster_11</title>
<polygon fill="transparent" stroke="transparent" points="8,-9 8,-314 336,-314 336,-9 8,-9"/>
<text text-anchor="middle" x="59" y="-282.8" font-family="Helvetica,sans-Serif" font-size="14.00" fill="#4d4d4d">Python Server</text>
</g>
<g id="clust2" class="cluster">
<title>cluster_thread_2</title>
<polygon fill="#91a4a8" stroke="transparent" points="18,-124 18,-265 326,-265 326,-124 18,-124"/>
<text text-anchor="middle" x="94.5" y="-241" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#edebdf">PerspectiveManager &#45; Thread 2</text>
</g>
<g id="clust3" class="cluster">
<title>cluster_thread_1</title>
<polygon fill="#91a4a8" stroke="transparent" points="18,-19 18,-114 326,-114 326,-19 18,-19"/>
<text text-anchor="middle" x="94.5" y="-90" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#edebdf">PerspectiveManager &#45; Thread 1</text>
</g>
<g id="clust4" class="cluster">
<title>cluster_browser</title>
<polygon fill="#cadee1" stroke="#cadee1" points="344,-8 344,-483 910,-483 910,-8 344,-8"/>
<text text-anchor="middle" x="377" y="-451.8" font-family="Helvetica,sans-Serif" font-size="14.00" fill="#4d4d4d">Browser</text>
</g>
<g id="clust5" class="cluster">
<title>cluster_2</title>
<polygon fill="#2d4c68" stroke="transparent" points="354,-231 354,-434 662,-434 662,-231 354,-231"/>
<text text-anchor="middle" x="391.5" y="-410" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#edebdf">WebWorker 1</text>
</g>
<g id="clust7" class="cluster">
<title>cluster_webworker2</title>
<polygon fill="#2d4c68" stroke="transparent" points="354,-120 354,-215 662,-215 662,-120 354,-120"/>
<text text-anchor="middle" x="391.5" y="-191" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#edebdf">WebWorker 2</text>
</g>
<g id="clust9" class="cluster">
<title>cluster_41</title>
<polygon fill="white" stroke="transparent" points="678,-18 678,-434 900,-434 900,-18 678,-18"/>
<text text-anchor="middle" x="700" y="-414.6" font-family="monospace" font-size="8.00" fill="#4d4d4d">&lt;html&gt;</text>
</g>
<!-- table_thread_2 -->
<g id="node1" class="node">
<title>table_thread_2</title>
<polygon fill="#e6e2da" stroke="transparent" points="100,-197 28,-197 28,-161 100,-161 100,-197"/>
<text text-anchor="middle" x="64" y="-177.1" font-family="monospace" font-size="8.00">table(df)</text>
</g>
<!-- view_thread_2 -->
<g id="node2" class="node">
<title>view_thread_2</title>
<polygon fill="#edebdf" stroke="transparent" points="316,-170 136,-170 136,-134 316,-134 316,-170"/>
<text text-anchor="start" x="144" y="-159.1" font-family="monospace" font-size="8.00">view({</text>
<text text-anchor="start" x="144" y="-150.1" font-family="monospace" font-size="8.00"> &#160;&#160;&#160;group_by: [&quot;State&quot;]]</text>
<text text-anchor="start" x="144" y="-141.1" font-family="monospace" font-size="8.00">})</text>
</g>
<!-- table_thread_2&#45;&gt;view_thread_2 -->
<g id="edge2" class="edge">
<title>table_thread_2&#45;&gt;view_thread_2</title>
<path fill="none" stroke="#edebdf" d="M100.11,-173.07C108.61,-171.64 118.1,-170.04 127.95,-168.38"/>
<polygon fill="#edebdf" stroke="#edebdf" points="128.49,-171.13 135.91,-167.03 127.56,-165.6 128.49,-171.13"/>
</g>
<!-- view_thread_2_2 -->
<g id="node3" class="node">
<title>view_thread_2_2</title>
<polygon fill="#edebdf" stroke="transparent" points="316,-224 136,-224 136,-188 316,-188 316,-224"/>
<text text-anchor="start" x="144" y="-213.1" font-family="monospace" font-size="8.00">view({</text>
<text text-anchor="start" x="144" y="-204.1" font-family="monospace" font-size="8.00"> &#160;&#160;&#160;group_by: [&quot;City&quot;]]</text>
<text text-anchor="start" x="144" y="-195.1" font-family="monospace" font-size="8.00">})</text>
</g>
<!-- table_thread_2&#45;&gt;view_thread_2_2 -->
<g id="edge1" class="edge">
<title>table_thread_2&#45;&gt;view_thread_2_2</title>
<path fill="none" stroke="#edebdf" d="M100.11,-184.93C108.61,-186.36 118.1,-187.96 127.95,-189.62"/>
<polygon fill="#edebdf" stroke="#edebdf" points="127.56,-192.4 135.91,-190.97 128.49,-186.87 127.56,-192.4"/>
</g>
<!-- table12 -->
<g id="node11" class="node">
<title>table12</title>
<polygon fill="#e6e2da" stroke="transparent" points="436,-170 364,-170 364,-134 436,-134 436,-170"/>
<text text-anchor="middle" x="400" y="-150.1" font-family="monospace" font-size="8.00">table(...)</text>
</g>
<!-- view_thread_2&#45;&gt;table12 -->
<g id="edge8" class="edge">
<title>view_thread_2&#45;&gt;table12</title>
<path fill="none" stroke="#d1a043" stroke-width="2" stroke-dasharray="5,2" d="M316.02,-152C332.91,-152 349.64,-152 363.61,-152"/>
</g>
<!-- table_thread_1 -->
<g id="node4" class="node">
<title>table_thread_1</title>
<polygon fill="#e6e2da" stroke="transparent" points="100,-69 28,-69 28,-33 100,-33 100,-69"/>
<text text-anchor="middle" x="64" y="-49.1" font-family="monospace" font-size="8.00">table(arrow)</text>
</g>
<!-- view_thread_1 -->
<g id="node5" class="node">
<title>view_thread_1</title>
<polygon fill="#edebdf" stroke="transparent" points="316,-73 136,-73 136,-29 316,-29 316,-73"/>
<text text-anchor="start" x="144" y="-62.6" font-family="monospace" font-size="8.00">view({</text>
<text text-anchor="start" x="144" y="-53.6" font-family="monospace" font-size="8.00"> &#160;&#160;&#160;group_by:&#39;[&quot;State&quot;]</text>
<text text-anchor="start" x="144" y="-44.6" font-family="monospace" font-size="8.00"> &#160;&#160;&#160;split_by:&#39;[&quot;Segment&quot;]&#39;</text>
<text text-anchor="start" x="144" y="-35.6" font-family="monospace" font-size="8.00">})</text>
</g>
<!-- table_thread_1&#45;&gt;view_thread_1 -->
<g id="edge3" class="edge">
<title>table_thread_1&#45;&gt;view_thread_1</title>
<path fill="none" stroke="#edebdf" d="M100.11,-51C108.52,-51 117.91,-51 127.66,-51"/>
<polygon fill="#edebdf" stroke="#edebdf" points="127.91,-53.8 135.91,-51 127.91,-48.2 127.91,-53.8"/>
</g>
<!-- viewer4 -->
<g id="node19" class="node">
<title>viewer4</title>
<polygon fill="white" stroke="#b3b3b3" points="890,-72 688,-72 688,-28 890,-28 890,-72"/>
<text text-anchor="start" x="696" y="-61.6" font-family="monospace" font-size="8.00">&lt;perspective&#45;viewer</text>
<text text-anchor="start" x="696" y="-52.6" font-family="monospace" font-size="8.00"> &#160;&#160;&#160;view=&quot;heatmap&quot;</text>
<text text-anchor="start" x="696" y="-43.6" font-family="monospace" font-size="8.00"> &#160;&#160;&#160;row&#45;pivots=&#39;[&quot;State&quot;]</text>
<text text-anchor="start" x="696" y="-34.6" font-family="monospace" font-size="8.00"> &#160;&#160;&#160;column&#45;pivots=&#39;[&quot;Segment&quot;]&#39;&gt;</text>
</g>
<!-- view_thread_1&#45;&gt;viewer4 -->
<g id="edge12" class="edge">
<title>view_thread_1&#45;&gt;viewer4</title>
<path fill="none" stroke="#d1a043" stroke-width="2" stroke-dasharray="5,2" d="M316.2,-50.84C417.45,-50.66 582.21,-50.37 687.8,-50.18"/>
</g>
<!-- table1 -->
<g id="node6" class="node">
<title>table1</title>
<polygon fill="#e6e2da" stroke="transparent" points="436,-364 364,-364 364,-328 436,-328 436,-364"/>
<text text-anchor="middle" x="400" y="-344.1" font-family="monospace" font-size="8.00">table(csv)</text>
</g>
<!-- view1 -->
<g id="node8" class="node">
<title>view1</title>
<polygon fill="#edebdf" stroke="transparent" points="652,-339 472,-339 472,-295 652,-295 652,-339"/>
<text text-anchor="start" x="480" y="-328.6" font-family="monospace" font-size="8.00">view({</text>
<text text-anchor="start" x="480" y="-319.6" font-family="monospace" font-size="8.00"> &#160;&#160;&#160;group_by: [&quot;Category&quot;]</text>
<text text-anchor="start" x="480" y="-310.6" font-family="monospace" font-size="8.00"> &#160;&#160;&#160;filter: [[&quot;State&quot;,&quot;==&quot;,&quot;Texas&quot;]]</text>
<text text-anchor="start" x="480" y="-301.6" font-family="monospace" font-size="8.00">})</text>
</g>
<!-- table1&#45;&gt;view1 -->
<g id="edge4" class="edge">
<title>table1&#45;&gt;view1</title>
<path fill="none" stroke="#edebdf" d="M436.11,-339.64C444.61,-338.1 454.1,-336.38 463.95,-334.59"/>
<polygon fill="#edebdf" stroke="#edebdf" points="464.54,-337.33 471.91,-333.15 463.54,-331.82 464.54,-337.33"/>
</g>
<!-- view2 -->
<g id="node9" class="node">
<title>view2</title>
<polygon fill="#edebdf" stroke="transparent" points="652,-393 472,-393 472,-357 652,-357 652,-393"/>
<text text-anchor="start" x="480" y="-382.1" font-family="monospace" font-size="8.00">view({</text>
<text text-anchor="start" x="480" y="-373.1" font-family="monospace" font-size="8.00"> &#160;&#160;&#160;group_by: [&quot;Sub&#45;Category&quot;]</text>
<text text-anchor="start" x="480" y="-364.1" font-family="monospace" font-size="8.00">})</text>
</g>
<!-- table1&#45;&gt;view2 -->
<g id="edge5" class="edge">
<title>table1&#45;&gt;view2</title>
<path fill="none" stroke="#edebdf" d="M436.11,-352.36C444.61,-353.9 454.1,-355.62 463.95,-357.41"/>
<polygon fill="#edebdf" stroke="#edebdf" points="463.54,-360.18 471.91,-358.85 464.54,-354.67 463.54,-360.18"/>
</g>
<!-- table_remote_view -->
<g id="node7" class="node">
<title>table_remote_view</title>
<polygon fill="#e6e2da" stroke="transparent" points="436,-277 364,-277 364,-241 436,-241 436,-277"/>
<text text-anchor="middle" x="400" y="-257.1" font-family="monospace" font-size="8.00">table(json)</text>
</g>
<!-- view3 -->
<g id="node10" class="node">
<title>view3</title>
<polygon fill="#edebdf" stroke="transparent" points="652,-277 472,-277 472,-241 652,-241 652,-277"/>
<text text-anchor="start" x="480" y="-257.1" font-family="monospace" font-size="8.00">view()</text>
</g>
<!-- table_remote_view&#45;&gt;view3 -->
<g id="edge6" class="edge">
<title>table_remote_view&#45;&gt;view3</title>
<path fill="none" stroke="#edebdf" d="M436.11,-259C444.52,-259 453.91,-259 463.66,-259"/>
<polygon fill="#edebdf" stroke="#edebdf" points="463.91,-261.8 471.91,-259 463.91,-256.2 463.91,-261.8"/>
</g>
<!-- viewer1 -->
<g id="node13" class="node">
<title>viewer1</title>
<polygon fill="white" stroke="#b3b3b3" points="890,-344 688,-344 688,-300 890,-300 890,-344"/>
<text text-anchor="start" x="696" y="-333.6" font-family="monospace" font-size="8.00">&lt;perspective&#45;viewer</text>
<text text-anchor="start" x="696" y="-324.6" font-family="monospace" font-size="8.00"> &#160;&#160;&#160;view=&quot;Y Bar&quot;</text>
<text text-anchor="start" x="696" y="-315.6" font-family="monospace" font-size="8.00"> &#160;&#160;&#160;row&#45;pivots=&#39;[&quot;Category&quot;]&#39;</text>
<text text-anchor="start" x="696" y="-306.6" font-family="monospace" font-size="8.00"> &#160;&#160;&#160;filters=&#39;[[&quot;State&quot;,&quot;==&quot;,&quot;Texas&quot;]]&#39;&gt;</text>
</g>
<!-- view1&#45;&gt;viewer1 -->
<g id="edge9" class="edge">
<title>view1&#45;&gt;viewer1</title>
<path fill="none" stroke="#000000" stroke-width="2" stroke-dasharray="5,2" d="M652.25,-318.98C663.9,-319.24 675.91,-319.51 687.73,-319.77"/>
</g>
<!-- viewer2 -->
<g id="node14" class="node">
<title>viewer2</title>
<polygon fill="white" stroke="lightgrey" points="890,-398 688,-398 688,-362 890,-362 890,-398"/>
<text text-anchor="start" x="696" y="-387.1" font-family="monospace" font-size="8.00">&lt;perspective&#45;viewer</text>
<text text-anchor="start" x="696" y="-378.1" font-family="monospace" font-size="8.00"> &#160;&#160;&#160;view=&quot;xy_scatter&quot;</text>
<text text-anchor="start" x="696" y="-369.1" font-family="monospace" font-size="8.00"> &#160;&#160;&#160;row&#45;pivots=&#39;[&quot;Sub&#45;Category&quot;]&#39;&gt;</text>
</g>
<!-- view2&#45;&gt;viewer2 -->
<g id="edge10" class="edge">
<title>view2&#45;&gt;viewer2</title>
<path fill="none" stroke="#000000" stroke-width="2" stroke-dasharray="5,2" d="M652.25,-376.98C663.9,-377.24 675.91,-377.51 687.73,-377.77"/>
</g>
<!-- viewer3 -->
<g id="node15" class="node">
<title>viewer3</title>
<polygon fill="white" stroke="#b3b3b3" points="890,-282 688,-282 688,-246 890,-246 890,-282"/>
<text text-anchor="start" x="696" y="-266.6" font-family="monospace" font-size="8.00">&lt;perspective&#45;viewer</text>
<text text-anchor="start" x="696" y="-257.6" font-family="monospace" font-size="8.00"> &#160;&#160;&#160;view=&quot;grid&quot;&gt;</text>
</g>
<!-- view3&#45;&gt;viewer3 -->
<g id="edge11" class="edge">
<title>view3&#45;&gt;viewer3</title>
<path fill="none" stroke="#000000" stroke-width="2" stroke-dasharray="5,2" d="M652.25,-260.98C663.9,-261.24 675.91,-261.51 687.73,-261.77"/>
</g>
<!-- view12 -->
<g id="node12" class="node">
<title>view12</title>
<polygon fill="#edebdf" stroke="transparent" points="652,-174 472,-174 472,-130 652,-130 652,-174"/>
<text text-anchor="start" x="480" y="-163.6" font-family="monospace" font-size="8.00">view({</text>
<text text-anchor="start" x="480" y="-154.6" font-family="monospace" font-size="8.00"> &#160;&#160;&#160;group_by: [&quot;Category&quot;]</text>
<text text-anchor="start" x="480" y="-145.6" font-family="monospace" font-size="8.00"> &#160;&#160;&#160;filter: [[&quot;State&quot;,&quot;==&quot;,&quot;Texas&quot;]]</text>
<text text-anchor="start" x="480" y="-136.6" font-family="monospace" font-size="8.00">})</text>
</g>
<!-- table12&#45;&gt;view12 -->
<g id="edge7" class="edge">
<title>table12&#45;&gt;view12</title>
<path fill="none" stroke="#e6e2da" d="M436.11,-152C444.52,-152 453.91,-152 463.66,-152"/>
<polygon fill="#e6e2da" stroke="#e6e2da" points="463.91,-154.8 471.91,-152 463.91,-149.2 463.91,-154.8"/>
</g>
<!-- viewer5 -->
<g id="node17" class="node">
<title>viewer5</title>
<polygon fill="white" stroke="#b3b3b3" points="890,-174 688,-174 688,-130 890,-130 890,-174"/>
<text text-anchor="start" x="696" y="-163.6" font-family="monospace" font-size="8.00">&lt;perspective&#45;viewer</text>
<text text-anchor="start" x="696" y="-154.6" font-family="monospace" font-size="8.00"> &#160;&#160;&#160;view=&quot;heatmap&quot;</text>
<text text-anchor="start" x="696" y="-145.6" font-family="monospace" font-size="8.00"> &#160;&#160;&#160;row&#45;pivots=&#39;[&quot;State&quot;]</text>
<text text-anchor="start" x="696" y="-136.6" font-family="monospace" font-size="8.00"> &#160;&#160;&#160;column&#45;pivots=&#39;[&quot;Segment&quot;]&#39;&gt;</text>
</g>
<!-- view12&#45;&gt;viewer5 -->
<g id="edge13" class="edge">
<title>view12&#45;&gt;viewer5</title>
<path fill="none" stroke="#000000" stroke-width="2" stroke-dasharray="5,2" d="M652.25,-152C663.9,-152 675.91,-152 687.73,-152"/>
</g>
<!-- viewerN -->
<!-- viewerZ -->
</g>
</svg>

After

Width:  |  Height:  |  Size: 15 KiB

@@ -0,0 +1,39 @@
# Client-only
<img src="./architecture.sub1.svg" />
_For static datasets, datasets provided by the user, and simple server-less and
read-only web applications._
In this design, Perspective is run as a client Browser WebAssembly library, the
dataset is downloaded entirely to the client and all calculations and UI
interactions are performed locally. Interactive performance is very good, using
WebAssembly engine for near-native runtime plus WebWorker isolation for parallel
rendering within the browser. Operations like scrolling and creating new views
are responsive. However, the entire dataset must be downloaded to the client.
Perspective is not a typical browser component, and datset sizes of 1gb+ in
Apache Arrow format will load fine with good interactive performance!
Horizontal scaling is a non-issue, since here is no concurrent state to scale,
and only uses client-side computation via WebAssembly client. Client-only
perspective can support as many concurrent users as can download the web
application itself. Once the data is loaded, no server connection is needed and
all operations occur in the client browser, imparting no additional runtime cost
on the server beyond initial load. This also means updates and edits are local
to the browser client and will be lost when the page is refreshed, unless
otherwise persisted by your application.
As the client-only design starts with creating a client-side Perspective
`Table`, data can be provided by any standard web service in any Perspective
compatible format (JSON, CSV or Apache Arrow).
## Javascript client
```javascript
const worker = await perspective.worker();
const table = await worker.table(csv);
const viewer = document.createElement("perspective-viewer");
document.body.appendChild(viewer);
await viewer.load(table);
```
@@ -0,0 +1,58 @@
# Client/Server replicated
<img src="./architecture.sub2.svg" />
_For medium-sized, real-time, synchronized and/or editable data sets with many
concurrent users._
The dataset is instantiated in-memory with a Python or Node.js Perspective
server, and web applications create duplicates of these tables in a local
WebAssembly client in the browser, synchonized efficiently to the server via
Apache Arrow. This design scales well with additional concurrent users, as
browsers only need to download the initial data set and subsequent update
deltas, while operations like scrolling, pivots, sorting, etc. are performed on
the client.
Python servers can make especially good use of additional threads, as
Perspective will release the GIL for almost all operations. Interactive
performance on the client is very good and identical to client-only
architecture. Updates and edits are seamlessly synchonized across clients via
their virtual server counterparts using websockets and Apache Arrow.
## Python and Tornado server
```python
from perspective import Server, PerspectiveTornadoHandler
server = Server()
client = server.new_local_client()
client.table(csv, name="my_table")
routes = [(
r"/websocket",
perspective.handlers.tornado.PerspectiveTornadoHandler,
{"perspective_server": server},
)]
app = tornado.web.Application(routes)
app.listen(8080)
loop = tornado.ioloop.IOLoop.current()
loop.start()
```
## Javascript client
Perspective's websocket client interfaces with the Python server, then
_replicates_ the server-side Table.
```javascript
const websocket = await perspective.websocket("ws://localhost:8080");
const server_table = await websocket.open_table("my_table");
const server_view = await server_table.view();
const worker = await perspective.worker();
const client_table = await worker.table(server_view);
const viewer = document.createElement("perspective-viewer");
document.body.appendChild(viewer);
await viewer.load(client_table);
```
@@ -0,0 +1,33 @@
# Server-only
<img src="./architecture.sub3.svg" />
_For extremely large datasets with a small number of concurrent users._
The dataset is instantiated in-memory with a Python or Node.js server, and web
applications connect virtually. Has very good initial load performance, since no
data is downloaded. Group-by and other operations will run column-parallel if
configured.
But interactive performance is poor, as every user interaction must page the
server to render. Operations like scrolling are not as responsive and can be
impacted by network latency. Web applications must be "always connected" to the
server via WebSocket. Disconnecting will prevent any interaction, scrolling,
etc. of the UI. Does not use WebAssembly.
Each connected browser will impact server performance as long as the connection
is open, which in turn impacts interactive performance of every client. This
ultimately limits the horizontal scalabity of this architecture. Since each
client reads the perspective `Table` virtually, changes like edits and updates
are automatically reflected to all clients and persist across browser refresh.
Using the same Python server as the previous design, we can simply skip the
intermediate WebAssembly `Table` and pass the virtual table directly to `load()`
```javascript
const websocket = await perspective.websocket("ws://localhost:8080");
const server_table = await websocket.open_table("my_table");
const viewer = document.createElement("perspective-viewer");
document.body.appendChild(viewer);
await viewer.load(server_table);
```