Skip to content

Commit 6516cb9

Browse files
committed
init to include vega-based visuals as export views
1 parent a2f132e commit 6516cb9

8 files changed

Lines changed: 442 additions & 0 deletions

File tree

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
namespace PSGraph.Model.VegaDataModels;
2+
3+
public class GraphRecord
4+
{
5+
public int id;
6+
public string name;
7+
public int parent;
8+
}

PSGraph.Tests/PSGraph.Tests.csproj

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
<ItemGroup>
3434
<ProjectReference Include="..\PSGraphv2\PSGraph.csproj" />
3535
<ProjectReference Include="..\PSGraph.Common\PSGraph.Common.csproj" />
36+
<ProjectReference Include="..\PSGraph.Vega.Extensions\PSGraph.Vega.Extensions.csproj" />
3637
</ItemGroup>
3738

3839
<ItemGroup>
@@ -43,6 +44,10 @@
4344
<None Update="Data/vms.graphml">
4445
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
4546
</None>
47+
<Content Include="..\PSGraph.Vega.Extensions\Assets\**\*.*">
48+
<Link>Assets\%(RecursiveDir)%(Filename)%(Extension)</Link>
49+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
50+
</Content>
4651
</ItemGroup>
4752

4853
</Project>
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
using PSGraph.Model.VegaDataModels;
2+
using Newtonsoft.Json.Linq;
3+
using PSGraph.Vega.Extensions;
4+
using FluentAssertions;
5+
6+
namespace PSGraph.Tests
7+
{
8+
public class VegaDataConverterTests
9+
{
10+
[Fact]
11+
public async Task ShouldEmbedGraphRecordsIntoVegaTemplate()
12+
{
13+
var graph = GraphTestData.SimpleTestGraph5;
14+
15+
List<GraphRecord> records = graph.ConvertToParentChildList();
16+
17+
var currentDir = System.IO.Directory.GetCurrentDirectory();
18+
var testTemplatePath = System.IO.Path.Combine(currentDir, "Assets", "vega.tree.layout.json");
19+
string template = File.ReadAllText(testTemplatePath);
20+
21+
var vega = JObject.Parse(template);
22+
23+
var dataToken = vega["data"];
24+
25+
if (dataToken is JArray dataArray &&
26+
dataArray.Count > 0 &&
27+
dataArray[0] is JObject firstData &&
28+
firstData["values"] != null)
29+
{
30+
firstData["values"] = JArray.FromObject(records);
31+
}
32+
else
33+
{
34+
Console.WriteLine("Поле 'values' отсутствует.");
35+
}
36+
37+
string json = vega.ToString(Newtonsoft.Json.Formatting.Indented);
38+
39+
// Сохранение в файл
40+
File.WriteAllText("vega.json", json);
41+
}
42+
}
43+
}
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
{
2+
"$schema": "https://vega.github.io/schema/vega/v5.json",
3+
"description": "A node-link diagram with force-directed layout, depicting character co-occurrence in the novel Les Misérables.",
4+
"width": 700,
5+
"height": 500,
6+
"padding": 0,
7+
"autosize": "none",
8+
9+
"signals": [
10+
{ "name": "cx", "update": "width / 2" },
11+
{ "name": "cy", "update": "height / 2" },
12+
{ "name": "nodeRadius", "value": 8,
13+
"bind": {"input": "range", "min": 1, "max": 50, "step": 1} },
14+
{ "name": "nodeCharge", "value": -30,
15+
"bind": {"input": "range", "min":-100, "max": 10, "step": 1} },
16+
{ "name": "linkDistance", "value": 30,
17+
"bind": {"input": "range", "min": 5, "max": 100, "step": 1} },
18+
{ "name": "static", "value": true,
19+
"bind": {"input": "checkbox"} },
20+
{
21+
"description": "State variable for active node fix status.",
22+
"name": "fix", "value": false,
23+
"on": [
24+
{
25+
"events": "symbol:pointerout[!event.buttons], window:pointerup",
26+
"update": "false"
27+
},
28+
{
29+
"events": "symbol:pointerover",
30+
"update": "fix || true"
31+
},
32+
{
33+
"events": "[symbol:pointerdown, window:pointerup] > window:pointermove!",
34+
"update": "xy()",
35+
"force": true
36+
}
37+
]
38+
},
39+
{
40+
"description": "Graph node most recently interacted with.",
41+
"name": "node", "value": null,
42+
"on": [
43+
{
44+
"events": "symbol:pointerover",
45+
"update": "fix === true ? item() : node"
46+
}
47+
]
48+
},
49+
{
50+
"description": "Flag to restart Force simulation upon data changes.",
51+
"name": "restart", "value": false,
52+
"on": [
53+
{"events": {"signal": "fix"}, "update": "fix && fix.length"}
54+
]
55+
}
56+
],
57+
58+
"data": [
59+
{
60+
"name": "node-data",
61+
"values": [],
62+
"format": {"type": "json", "property": "nodes"}
63+
},
64+
{
65+
"name": "link-data",
66+
"url": "data/miserables.json",
67+
"format": {"type": "json", "property": "links"}
68+
}
69+
],
70+
71+
"scales": [
72+
{
73+
"name": "color",
74+
"type": "ordinal",
75+
"domain": {"data": "node-data", "field": "group"},
76+
"range": {"scheme": "category20c"}
77+
}
78+
],
79+
80+
"marks": [
81+
{
82+
"name": "nodes",
83+
"type": "symbol",
84+
"zindex": 1,
85+
86+
"from": {"data": "node-data"},
87+
"on": [
88+
{
89+
"trigger": "fix",
90+
"modify": "node",
91+
"values": "fix === true ? {fx: node.x, fy: node.y} : {fx: fix[0], fy: fix[1]}"
92+
},
93+
{
94+
"trigger": "!fix",
95+
"modify": "node", "values": "{fx: null, fy: null}"
96+
}
97+
],
98+
99+
"encode": {
100+
"enter": {
101+
"fill": {"scale": "color", "field": "group"},
102+
"stroke": {"value": "white"}
103+
},
104+
"update": {
105+
"size": {"signal": "2 * nodeRadius * nodeRadius"},
106+
"cursor": {"value": "pointer"}
107+
}
108+
},
109+
110+
"transform": [
111+
{
112+
"type": "force",
113+
"iterations": 300,
114+
"restart": {"signal": "restart"},
115+
"static": {"signal": "static"},
116+
"signal": "force",
117+
"forces": [
118+
{"force": "center", "x": {"signal": "cx"}, "y": {"signal": "cy"}},
119+
{"force": "collide", "radius": {"signal": "nodeRadius"}},
120+
{"force": "nbody", "strength": {"signal": "nodeCharge"}},
121+
{"force": "link", "links": "link-data", "distance": {"signal": "linkDistance"}}
122+
]
123+
}
124+
]
125+
},
126+
{
127+
"type": "path",
128+
"from": {"data": "link-data"},
129+
"interactive": false,
130+
"encode": {
131+
"update": {
132+
"stroke": {"value": "#ccc"},
133+
"strokeWidth": {"value": 0.5}
134+
}
135+
},
136+
"transform": [
137+
{
138+
"type": "linkpath",
139+
"require": {"signal": "force"},
140+
"shape": "line",
141+
"sourceX": "datum.source.x", "sourceY": "datum.source.y",
142+
"targetX": "datum.target.x", "targetY": "datum.target.y"
143+
}
144+
]
145+
}
146+
]
147+
}
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
{
2+
"$schema": "https://vega.github.io/schema/vega/v5.json",
3+
"description": "An example of Cartesian layouts for a node-link diagram of hierarchical data.",
4+
"width": 600,
5+
"height": 1600,
6+
"padding": 5,
7+
8+
"signals": [
9+
{
10+
"name": "labels", "value": true,
11+
"bind": {"input": "checkbox"}
12+
},
13+
{
14+
"name": "layout", "value": "tidy",
15+
"bind": {"input": "radio", "options": ["tidy", "cluster"]}
16+
},
17+
{
18+
"name": "links", "value": "diagonal",
19+
"bind": {
20+
"input": "select",
21+
"options": ["line", "curve", "diagonal", "orthogonal"]
22+
}
23+
},
24+
{
25+
"name": "separation", "value": false,
26+
"bind": {"input": "checkbox"}
27+
}
28+
],
29+
30+
"data": [
31+
{
32+
"name": "tree",
33+
"values": [],
34+
"transform": [
35+
{
36+
"type": "stratify",
37+
"key": "id",
38+
"parentKey": "parent"
39+
},
40+
{
41+
"type": "tree",
42+
"method": {"signal": "layout"},
43+
"size": [{"signal": "height"}, {"signal": "width - 100"}],
44+
"separation": {"signal": "separation"},
45+
"as": ["y", "x", "depth", "children"]
46+
}
47+
]
48+
},
49+
{
50+
"name": "links",
51+
"source": "tree",
52+
"transform": [
53+
{ "type": "treelinks" },
54+
{
55+
"type": "linkpath",
56+
"orient": "horizontal",
57+
"shape": {"signal": "links"}
58+
}
59+
]
60+
}
61+
],
62+
63+
"scales": [
64+
{
65+
"name": "color",
66+
"type": "linear",
67+
"range": {"scheme": "magma"},
68+
"domain": {"data": "tree", "field": "depth"},
69+
"zero": true
70+
}
71+
],
72+
73+
"marks": [
74+
{
75+
"type": "path",
76+
"from": {"data": "links"},
77+
"encode": {
78+
"update": {
79+
"path": {"field": "path"},
80+
"stroke": {"value": "#ccc"}
81+
}
82+
}
83+
},
84+
{
85+
"type": "symbol",
86+
"from": {"data": "tree"},
87+
"encode": {
88+
"enter": {
89+
"size": {"value": 100},
90+
"stroke": {"value": "#fff"}
91+
},
92+
"update": {
93+
"x": {"field": "x"},
94+
"y": {"field": "y"},
95+
"fill": {"scale": "color", "field": "depth"}
96+
}
97+
}
98+
},
99+
{
100+
"type": "text",
101+
"from": {"data": "tree"},
102+
"encode": {
103+
"enter": {
104+
"text": {"field": "name"},
105+
"fontSize": {"value": 9},
106+
"baseline": {"value": "middle"}
107+
},
108+
"update": {
109+
"x": {"field": "x"},
110+
"y": {"field": "y"},
111+
"dx": {"signal": "datum.children ? -7 : 7"},
112+
"align": {"signal": "datum.children ? 'right' : 'left'"},
113+
"opacity": {"signal": "labels ? 1 : 0"}
114+
}
115+
}
116+
}
117+
]
118+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net9.0</TargetFramework>
5+
<ImplicitUsings>enable</ImplicitUsings>
6+
<Nullable>enable</Nullable>
7+
</PropertyGroup>
8+
9+
<ItemGroup>
10+
<ProjectReference Include="..\PSGraph.Common\PSGraph.Common.csproj" />
11+
</ItemGroup>
12+
13+
</Project>

0 commit comments

Comments
 (0)