on main help sign in
searching and clicking in star chart
commita64ee9743b3488609aefa8455b85bf2276145a4b authorJohn Hyde <johnhydefour@gmail.com> dateThu 23 Jun 2022, 19:47 (4 years ago) parentd08b4ef
15 files changed+873−37
desk/app/astrolabe.hoon+2−2 desk/lib/astrolabe.hoon+5−2 ui/package-lock.json+102 ui/package.json+2 ui/scraps/tirrel-graph-main.js+198 ui/src/actions/sigma.ts+73 ui/src/components/HomeScreenTiles.svelte+9 ui/src/components/SearchBar.svelte+1−1 ui/src/components/StarChart.svelte+134 ui/src/lib/api.ts+4 ui/src/lib/colors.ts+7 ui/src/lib/graph.ts+213 ui/src/routes/Home.svelte+25−20 ui/src/styles/tailwind-output.css+95−11 ui/tailwind.config.cjs+3−1
desk/app/astrolabe.hoon+2−2
@@ -267,7 +267,7 @@
267267++ get-chart-data
268268 ^- chart-data
269269 =/ nas get-nas
270− :: =/ galaxies (gulf 0 15) :: todo remove
270+ :: =/ galaxies (gulf 0 30) :: todo remove
271271 %+ turn galaxies
272272 |= =ship
273273 =/ point (need (get:orm:naive points.nas ship))
@@ -292,7 +292,7 @@
292292 |= [nas=^state:naive =ship]
293293 ^- chart-data
294294 :: [*point-meta ~]~
295− %- (run-spawned-points chart-data ship nas %all)
295+ %- (run-spawned-points chart-data ship nas %unlocked)
296296 |= [agg=chart-data [spawn=^ship spawn-point=npoint]]
297297 :_ agg
298298 (get-point-chart-data nas spawn spawn-point)
desk/lib/astrolabe.hoon+5−2
@@ -146,12 +146,15 @@
146146 ++ point-meta
147147 |= m=^point-meta
148148 ^- (list [@tas json])
149− =/ main-pairs
149+ =/ main-pairs=(list [@tas json])
150150 :~
151151 p+(ship ship.m)
152152 :: spa-count+(numb spa-count.m)
153− [%d s+dom.m]
153+ :: [%d s+dom.m]
154154 ==
155+ =? main-pairs !=(dom.m %l1)
156+ :_ main-pairs
157+ d+s+dom.m
155158 ?: =(has.spo.m %same) main-pairs
156159 :_ main-pairs
157160 :- %s
ui/package-lock.json+102
@@ -13,7 +13,9 @@
1313 "@urbit/api": "^2.1.0",
1414 "@urbit/http-api": "^2.1.0",
1515 "bn.js": "^5.2.0",
16+ "graphology": "^0.24.1",
1617 "lodash": "^4.17.21",
18+ "sigma": "^2.3.1",
1719 "svelte-copy-to-clipboard": "^0.2.5",
1820 "tippy.js": "^6.3.7",
1921 "urbit-ob": "^5.0.1"
@@ -318,6 +320,11 @@
318320 "vite-plugin-rewrite-all": "^0.1.2"
319321 }
320322 },
323+ "node_modules/@yomguithereal/helpers": {
324+ "version": "1.1.1",
325+ "resolved": "https://registry.npmjs.org/@yomguithereal/helpers/-/helpers-1.1.1.tgz",
326+ "integrity": "sha512-UYvAq/XCA7xoh1juWDYsq3W0WywOB+pz8cgVnE1b45ZfdMhBvHDrgmSFG3jXeZSr2tMTYLGHFHON+ekG05Jebg=="
327+ },
321328 "node_modules/acorn": {
322329 "version": "7.4.1",
323330 "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz",
@@ -1148,6 +1155,14 @@
11481155 "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-2.0.3.tgz",
11491156 "integrity": "sha1-teEHm1n7XhuidxwKmTvgYKWMmbo="
11501157 },
1158+ "node_modules/events": {
1159+ "version": "3.3.0",
1160+ "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
1161+ "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==",
1162+ "engines": {
1163+ "node": ">=0.8.x"
1164+ }
1165+ },
11511166 "node_modules/fast-glob": {
11521167 "version": "3.2.9",
11531168 "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.9.tgz",
@@ -1325,6 +1340,32 @@
13251340 "integrity": "sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ==",
13261341 "dev": true
13271342 },
1343+ "node_modules/graphology": {
1344+ "version": "0.24.1",
1345+ "resolved": "https://registry.npmjs.org/graphology/-/graphology-0.24.1.tgz",
1346+ "integrity": "sha512-6lNz1PNTAe9Q6ioHKrXu0Lp047sgvOoHa4qmP/8mnJWCGv2iIZPQkuHPUb2/OWDWCqHpw2hKgJLJ55X/66xmHg==",
1347+ "dependencies": {
1348+ "events": "^3.3.0",
1349+ "obliterator": "^2.0.2"
1350+ },
1351+ "peerDependencies": {
1352+ "graphology-types": ">=0.24.0"
1353+ }
1354+ },
1355+ "node_modules/graphology-types": {
1356+ "version": "0.24.4",
1357+ "resolved": "https://registry.npmjs.org/graphology-types/-/graphology-types-0.24.4.tgz",
1358+ "integrity": "sha512-CSgmycWiviCctMFO86YoUTJN1t4/PLKC5Pos2Hite+7kCUXTr+mGlDUAOgpcKG1IfFaeL9VDmTjFpzs2rTnPWw==",
1359+ "peer": true
1360+ },
1361+ "node_modules/graphology-utils": {
1362+ "version": "2.5.2",
1363+ "resolved": "https://registry.npmjs.org/graphology-utils/-/graphology-utils-2.5.2.tgz",
1364+ "integrity": "sha512-ckHg8MXrXJkOARk56ZaSCM1g1Wihe2d6iTmz1enGOz4W/l831MBCKSayeFQfowgF8wd+PQ4rlch/56Vs/VZLDQ==",
1365+ "peerDependencies": {
1366+ "graphology-types": ">=0.23.0"
1367+ }
1368+ },
13281369 "node_modules/has": {
13291370 "version": "1.0.3",
13301371 "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
@@ -1769,6 +1810,11 @@
17691810 "node": ">= 6"
17701811 }
17711812 },
1813+ "node_modules/obliterator": {
1814+ "version": "2.0.4",
1815+ "resolved": "https://registry.npmjs.org/obliterator/-/obliterator-2.0.4.tgz",
1816+ "integrity": "sha512-lgHwxlxV1qIg1Eap7LgIeoBWIMFibOjbrYPIPJZcI1mmGAI2m3lNYpK12Y+GBdPQ0U1hRwSord7GIaawz962qQ=="
1817+ },
17721818 "node_modules/omit-deep": {
17731819 "version": "0.3.0",
17741820 "resolved": "https://registry.npmjs.org/omit-deep/-/omit-deep-0.3.0.tgz",
@@ -2334,6 +2380,16 @@
23342380 "node": ">=8"
23352381 }
23362382 },
2383+ "node_modules/sigma": {
2384+ "version": "2.3.1",
2385+ "resolved": "https://registry.npmjs.org/sigma/-/sigma-2.3.1.tgz",
2386+ "integrity": "sha512-YfQsVZf78CeIUv8EYLqp/yvhMwwBXGrbpy+rD1N2X9IqtJ11+VfQRjWfVqHDhjM8oPfWPsaJMtpWFQ8pIflvCA==",
2387+ "dependencies": {
2388+ "@yomguithereal/helpers": "^1.1.1",
2389+ "events": "^3.3.0",
2390+ "graphology-utils": "^2.5.0"
2391+ }
2392+ },
23372393 "node_modules/slash": {
23382394 "version": "4.0.0",
23392395 "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz",
@@ -3141,6 +3197,11 @@
31413197 "vite-plugin-rewrite-all": "^0.1.2"
31423198 }
31433199 },
3200+ "@yomguithereal/helpers": {
3201+ "version": "1.1.1",
3202+ "resolved": "https://registry.npmjs.org/@yomguithereal/helpers/-/helpers-1.1.1.tgz",
3203+ "integrity": "sha512-UYvAq/XCA7xoh1juWDYsq3W0WywOB+pz8cgVnE1b45ZfdMhBvHDrgmSFG3jXeZSr2tMTYLGHFHON+ekG05Jebg=="
3204+ },
31443205 "acorn": {
31453206 "version": "7.4.1",
31463207 "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz",
@@ -3701,6 +3762,11 @@
37013762 "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-2.0.3.tgz",
37023763 "integrity": "sha1-teEHm1n7XhuidxwKmTvgYKWMmbo="
37033764 },
3765+ "events": {
3766+ "version": "3.3.0",
3767+ "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
3768+ "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q=="
3769+ },
37043770 "fast-glob": {
37053771 "version": "3.2.9",
37063772 "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.9.tgz",
@@ -3828,6 +3894,27 @@
38283894 "integrity": "sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ==",
38293895 "dev": true
38303896 },
3897+ "graphology": {
3898+ "version": "0.24.1",
3899+ "resolved": "https://registry.npmjs.org/graphology/-/graphology-0.24.1.tgz",
3900+ "integrity": "sha512-6lNz1PNTAe9Q6ioHKrXu0Lp047sgvOoHa4qmP/8mnJWCGv2iIZPQkuHPUb2/OWDWCqHpw2hKgJLJ55X/66xmHg==",
3901+ "requires": {
3902+ "events": "^3.3.0",
3903+ "obliterator": "^2.0.2"
3904+ }
3905+ },
3906+ "graphology-types": {
3907+ "version": "0.24.4",
3908+ "resolved": "https://registry.npmjs.org/graphology-types/-/graphology-types-0.24.4.tgz",
3909+ "integrity": "sha512-CSgmycWiviCctMFO86YoUTJN1t4/PLKC5Pos2Hite+7kCUXTr+mGlDUAOgpcKG1IfFaeL9VDmTjFpzs2rTnPWw==",
3910+ "peer": true
3911+ },
3912+ "graphology-utils": {
3913+ "version": "2.5.2",
3914+ "resolved": "https://registry.npmjs.org/graphology-utils/-/graphology-utils-2.5.2.tgz",
3915+ "integrity": "sha512-ckHg8MXrXJkOARk56ZaSCM1g1Wihe2d6iTmz1enGOz4W/l831MBCKSayeFQfowgF8wd+PQ4rlch/56Vs/VZLDQ==",
3916+ "requires": {}
3917+ },
38313918 "has": {
38323919 "version": "1.0.3",
38333920 "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
@@ -4172,6 +4259,11 @@
41724259 "integrity": "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==",
41734260 "dev": true
41744261 },
4262+ "obliterator": {
4263+ "version": "2.0.4",
4264+ "resolved": "https://registry.npmjs.org/obliterator/-/obliterator-2.0.4.tgz",
4265+ "integrity": "sha512-lgHwxlxV1qIg1Eap7LgIeoBWIMFibOjbrYPIPJZcI1mmGAI2m3lNYpK12Y+GBdPQ0U1hRwSord7GIaawz962qQ=="
4266+ },
41754267 "omit-deep": {
41764268 "version": "0.3.0",
41774269 "resolved": "https://registry.npmjs.org/omit-deep/-/omit-deep-0.3.0.tgz",
@@ -4549,6 +4641,16 @@
45494641 "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
45504642 "dev": true
45514643 },
4644+ "sigma": {
4645+ "version": "2.3.1",
4646+ "resolved": "https://registry.npmjs.org/sigma/-/sigma-2.3.1.tgz",
4647+ "integrity": "sha512-YfQsVZf78CeIUv8EYLqp/yvhMwwBXGrbpy+rD1N2X9IqtJ11+VfQRjWfVqHDhjM8oPfWPsaJMtpWFQ8pIflvCA==",
4648+ "requires": {
4649+ "@yomguithereal/helpers": "^1.1.1",
4650+ "events": "^3.3.0",
4651+ "graphology-utils": "^2.5.0"
4652+ }
4653+ },
45524654 "slash": {
45534655 "version": "4.0.0",
45544656 "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz",
ui/package.json+2
@@ -37,7 +37,9 @@
3737 "@urbit/api": "^2.1.0",
3838 "@urbit/http-api": "^2.1.0",
3939 "bn.js": "^5.2.0",
40+ "graphology": "^0.24.1",
4041 "lodash": "^4.17.21",
42+ "sigma": "^2.3.1",
4143 "svelte-copy-to-clipboard": "^0.2.5",
4244 "tippy.js": "^6.3.7",
4345 "urbit-ob": "^5.0.1"
ui/scraps/tirrel-graph-main.jsnew file+198
@@ -0,0 +1,198 @@
1+// Let's first initialize sigma:
2+//
3+
4+let fetchArray = [];
5+for (let i = 0; i < 256; i++) {
6+ fetchArray.push(
7+ fetch(`./galaxies/${i.toString()}.json`).then(r => r.json())
8+ );
9+}
10+
11+let searchButton = document.getElementById('searchButton');
12+
13+Promise.all(fetchArray).then(values => {
14+ sigma.settings.minEdgeSize = 0;
15+ sigma.settings.maxEdgeSize = 0;
16+ sigma.settings.minNodeSize = 0;
17+ sigma.settings.maxNodeSize = 0;
18+ sigma.settings.zoomMin = 0.00001;
19+ sigma.settings.labelThreshold = 20;
20+ sigma.settings.font = "monospace";
21+
22+ let s = new sigma({
23+ renderer: {
24+ container: document.getElementById('container'),
25+ type: 'webgl'
26+ }
27+ });
28+ let i = 0;
29+
30+ let offsetX = 0;
31+ let offsetY = 0;
32+
33+ let a = 120000000;
34+ let b = 8000000;
35+ let theta = 0;
36+ while (i < values.length) {
37+ offsetX = 0 + (a + b * theta) * Math.cos(theta);
38+ offsetY = 0 + (a + b * theta) * Math.sin(theta);
39+
40+ galaxy(values[i], s, offsetX, offsetY);
41+
42+ theta = 0.12 * (i+1);
43+ i++;
44+ }
45+
46+ s.refresh();
47+
48+ let headerDiv = document.getElementById('header');
49+ headerDiv.style = 'opacity:1;';
50+
51+ let search = document.getElementById('header');
52+ let searchInput = document.getElementById('searchInput');
53+
54+ let searchFunc = () => {
55+ let ob = window['urbit-ob'].default;
56+ let point = s.graph.nodes(searchInput.value);
57+ if (!point) {
58+ return;
59+ }
60+
61+ let ratio = 0.05;
62+ if (ob.clan(searchInput.value) === 'star') {
63+ ratio = 0.018;
64+ } else if (ob.clan(searchInput.value) === 'planet') {
65+ ratio = 0.003;
66+ }
67+
68+ s.cameras['0'].goTo({
69+ x: point['read_cam0:x'],
70+ y: point['read_cam0:y'],
71+ ratio: ratio,
72+ angle: 0
73+ });
74+ };
75+
76+ searchButton.addEventListener('click', searchFunc);
77+ searchInput.addEventListener('keyup', (e) => {
78+ if (e.key === 'Enter') {
79+ searchFunc();
80+ }
81+ });
82+});
83+
84+const galaxy = (data, s, offsetX = 0, offsetY = 0) => {
85+ // Then, let's add some data to display:
86+ let ob = window['urbit-ob'].default;
87+
88+ for (let [key, val] of Object.entries(data)) {
89+ s.graph.addNode({
90+ id: ob.patp(key),
91+ label: ob.patp(key),
92+ size: 4,
93+ color: '#999',
94+ x: 0 + offsetX,
95+ y: 0 + offsetY
96+ });
97+
98+ let numOfStars = Object.entries(val).length;
99+ let starIter = 0;
100+ for (let [starKey, starVal] of Object.entries(val)) {
101+ const circle = 2 * Math.PI;
102+ let line = 40000 * 128;
103+ let starX = 0 + Math.cos(circle * starIter / numOfStars) * line;
104+ let starY = 0 + Math.sin(circle * starIter / numOfStars) * line;
105+
106+ s.graph.addNode({
107+ id: ob.patp(starKey),
108+ label: ob.patp(starKey),
109+ size: 1.5,
110+ color: '#444',
111+ x: starX + offsetX,
112+ y: starY + offsetY
113+ }).addEdge({
114+ id: `e-star${starKey}`,
115+ source: ob.patp(key),
116+ target: ob.patp(starKey),
117+ color: '#444',
118+ size: 0.2
119+ });
120+
121+ let starAngle = Math.atan2(starY, starX);
122+ let numOfPlanets = Object.entries(starVal).length;
123+ let planetIter = 0;
124+
125+ for (let [planetKey, planetVal] of Object.entries(starVal)) {
126+ let planetAngle = starAngle + (Math.PI * 2 * (planetIter / numOfPlanets));
127+
128+ let lineLen = 0;
129+ let lineColor = '#000';
130+ let nodeColor = '#000';
131+
132+ if (planetIter % 4 === 0) {
133+ lineLen = 19000;
134+ lineColor = '#000';
135+ nodeColor = '#000';
136+ } else if (planetIter % 4 === 1) {
137+ lineLen = 16000;
138+ lineColor = '#ccc';
139+ nodeColor = '#ccc';
140+ } else if (planetIter % 4 === 2) {
141+ lineLen = 13000;
142+ lineColor = '#555';
143+ nodeColor = '#555';
144+ } else if (planetIter % 4 === 3) {
145+ lineLen = 16000;
146+ lineColor = '#ccc';
147+ nodeColor = '#ccc';
148+ }
149+
150+ if (numOfStars < 5) {
151+ lineLen = lineLen * 144;
152+ } else if (numOfStars < 7) {
153+ lineLen = lineLen * 128;
154+ } else if (numOfStars < 9) {
155+ lineLen = lineLen * 96;
156+ } else if (numOfStars < 11) {
157+ lineLen = lineLen * 80;
158+ } else if (numOfStars < 13) {
159+ lineLen = lineLen * 64;
160+ } else if (numOfStars < 15) {
161+ lineLen = lineLen * 56;
162+ } else if (numOfStars < 17) {
163+ lineLen = lineLen * 48;
164+ } else if (numOfStars < 19) {
165+ lineLen = lineLen * 40;
166+ } else if (numOfStars < 21) {
167+ lineLen = lineLen * 36;
168+ } else if (numOfStars < 23) {
169+ lineLen = lineLen * 32;
170+ } else {
171+ lineLen = lineLen * 24;
172+ }
173+
174+ let planetX = starX + (lineLen * Math.cos(planetAngle));
175+ let planetY = starY + (lineLen * Math.sin(planetAngle));
176+
177+ s.graph.addNode({
178+ id: ob.patp(planetVal),
179+ label: ob.patp(planetVal),
180+ size: 0.2,
181+ color: nodeColor,
182+ x: planetX + offsetX,
183+ y: planetY + offsetY
184+ }).addEdge({
185+ id: `e-planet${planetVal}`,
186+ source: ob.patp(starKey),
187+ target: ob.patp(planetVal),
188+ size: 0.05,
189+ color: lineColor
190+ });
191+
192+ planetIter++;
193+ }
194+
195+ starIter++;
196+ }
197+ }
198+};
ui/src/actions/sigma.tsnew file+73
@@ -0,0 +1,73 @@
1+import tippy from 'tippy.js';
2+// import DocLink from '../components/docs/DocLink.svelte'; // TODO try to add a link in the tooltip somehow or something, like:
3+// const app = new DocLink({
4+// target: document.getElementBySelector('.tippy')
5+// })
6+
7+const defaultParams = {
8+ theme: 'astrolabe',
9+ delay: [500, 0],
10+};
11+
12+function getParams(paramsOrContent:any = {}, overrides = {}) {
13+ let params = paramsOrContent;
14+ if (typeof paramsOrContent === 'string') {
15+ params = { content: paramsOrContent };
16+ }
17+ return {
18+ ...defaultParams,
19+ ...params,
20+ ...overrides,
21+ };
22+}
23+
24+export default function tooltip(node, paramsOrContent: any = {}) {
25+ const onTrigger = () => {
26+ console.log('triggered');
27+ node.dispatchEvent(new CustomEvent("tooltipTriggered"));
28+ };
29+ const onUntrigger = () => {
30+ console.log('untriggered');
31+ node.dispatchEvent(new CustomEvent("tooltipUntriggered"));
32+ };
33+ const onClickOutside = (_, e) => {
34+ e.stopPropagation();
35+ };
36+ const listeners = {
37+ onTrigger,
38+ onUntrigger,
39+ onClickOutside,
40+ }
41+
42+ const params = getParams(paramsOrContent, listeners);
43+ const { content } = params;
44+
45+ // Let's make sure the "aria-label" attribute
46+ // is set so our element is accessible:
47+ // https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Techniques/Using_the_aria-label_attribute
48+ if (!node.getAttribute("aria-label")) node.setAttribute("aria-label", content);
49+
50+
51+ // Support any of the Tippy props by forwarding all "params":
52+ // https://atomiks.github.io/tippyjs/v6/all-props/
53+ const tip: any = tippy(node, params);
54+ const hideTip = (e) => {
55+ if (tip.state.isShown) {
56+ tip.hide();
57+ // e.stopPropagation();
58+ }
59+ }
60+ node.addEventListener('click', hideTip);
61+
62+
63+ return {
64+ // If the props change, let's update the Tippy instance:
65+ update: (newParams) => tip.setProps(getParams(newParams)),
66+
67+ // Clean up the Tippy instance on unmount:
68+ destroy: () => {
69+ node.removeEventListener('click', hideTip);
70+ tip.destroy();
71+ }
72+ };
73+};
ui/src/components/HomeScreenTiles.svelte+9
@@ -1,5 +1,6 @@
11<script lang="ts">
22 import { createEventDispatcher } from 'svelte';
3+ import { link } from 'svelte-spa-router'
34 import DocLink from './docs/DocLink.svelte';
45
56 const dispatch = createEventDispatcher();
@@ -30,4 +31,12 @@
3031 </div>
3132 </DocLink>
3233 </div>
34+ <a class="rounded-lg bg-navy border border-gold flex-grow max-w-s" use:link href="/chart">
35+ <p class="p-5 text-xl text-center text-white">
36+ <br/>
37+ Star Chart
38+ <br/>
39+ <br/>
40+ </p>
41+ </a>
3342</div>
ui/src/components/SearchBar.svelte+1−1
@@ -36,7 +36,7 @@
3636 }
3737</script>
3838
39−<div class="relative grow max-w-md rounded-full w-full">
39+<div class="relative max-w-md rounded-full w-full">
4040 <input type="text" class="p-4 py-2 w-full rounded-full"
4141 spellcheck="false"
4242 placeholder={placeholderText}
ui/src/components/StarChart.sveltenew file+134
@@ -0,0 +1,134 @@
1+<script lang="ts">
2+ import { push } from 'svelte-spa-router'
3+
4+ import Sigma from 'sigma';
5+ import Graph from 'graphology';
6+
7+ import { clan } from 'urbit-ob';
8+ import { cite, normalizeId } from '../lib/id';
9+ import { populateGraph } from '../lib/graph';
10+ import { getChartData } from '../lib/api';
11+ import { linkToShip } from '../lib/link';
12+
13+ export let patp: string;
14+ let s;
15+ let g = new Graph();
16+
17+ function sigma(container: HTMLElement, params: any = {}) {
18+ try {
19+ window['s'] = s = new Sigma(g, container, {
20+ renderLabels: false,
21+ // labelColor: { color: WHITE },
22+ labelFont: 'monospace',
23+ labelRenderedSizeThreshold: 9,
24+ zIndex: true,
25+ maxCameraRatio: 10,
26+ });
27+
28+ s.on('downNode', ({ node }) => {
29+ push(linkToShip(node));
30+ });
31+ } catch(e) {
32+ console.error('error in initializing sigma:', e);
33+ if (!params.retried) {
34+ setTimeout(() => sigma(container, { retried: true }), 50);
35+ }
36+ }
37+
38+ return {
39+ destroy() {
40+ s?.kill();
41+ }
42+ }
43+ }
44+
45+ let chartDataPromise: any;
46+ let rawChartData: any = null;
47+ $: {
48+ rawChartData = null;
49+ chartDataPromise = getChartData();
50+ chartDataPromise.then((info) => {
51+ rawChartData = info;
52+ }).catch((error) => {
53+ rawChartData = { error };
54+ });
55+ }
56+ $: rawChartData, populateGraph(g, rawChartData), s?.scheduleRefresh(), searchFunc();
57+
58+ let highlightedPatp;
59+ function searchFunc() {
60+ if (!s) return;
61+ if (highlightedPatp) {
62+ s.graph.removeNodeAttribute(highlightedPatp, 'highlighted');
63+ highlightedPatp = null;
64+ }
65+ if (!patp) return;
66+ let point = s.getNodeDisplayData(patp);
67+ if (!point) return;
68+ s.graph.setNodeAttribute(patp, 'highlighted', true);
69+ highlightedPatp = patp;
70+
71+ let ratio = 0.05;
72+ if (clan(patp) === 'star') {
73+ ratio = 0.035;
74+ } else if (clan(patp) === 'planet') {
75+ ratio = 0.0025;
76+ }
77+
78+ s.camera.animate({
79+ x: point.x,
80+ y: point.y,
81+ ratio: ratio,
82+ // ratio: 1,
83+ angle: 0
84+ }, {
85+ duration: 500,
86+ });
87+ s.scheduleRefresh();
88+ };
89+
90+ $: s, patp, searchFunc();
91+
92+ let helpBoxExpanded = false;
93+ let helpBoxClasses = '';
94+ $: {
95+ helpBoxClasses = helpBoxExpanded ?
96+ "" :
97+ "absolute border-r";
98+ // refreshGraph();
99+ if (s) {
100+ s.resize();
101+ s.scheduleRefresh();
102+ }
103+ }
104+</script>
105+
106+<div class="border border-gold bg-navy w-full grow flex flex-col">
107+ <div class="flex flex-wrap {helpBoxClasses} border-b border-gold bg-navy text-white z-10 cursor-pointer justify-center px-2 py-1 gap-4"
108+ on:click={() => helpBoxExpanded = !helpBoxExpanded }>
109+ {#if helpBoxExpanded}
110+ <div class="flex flex-wrap items-center gap-2">
111+ <div class="w-3 h-3 rounded-full bg-white"></div>
112+ Galaxy
113+ </div>
114+ <div class="flex flex-wrap items-center gap-2">
115+ <div class="w-3 h-3 rounded-full bg-gold"></div>
116+ Star
117+ </div>
118+ <div class="flex flex-wrap items-center gap-2">
119+ <div class="w-3 h-3 rounded-full bg-l1-blue"></div>
120+ L1 Planet
121+ </div>
122+ <div class="flex flex-wrap items-center gap-2">
123+ <div class="w-3 h-3 rounded-full bg-l2-green"></div>
124+ L2 Planet
125+ </div>
126+ {:else}
127+ Help ({patp})
128+ {/if}
129+ </div>
130+ <div class="grow flex">
131+ <div class="w-full" use:sigma>
132+ </div>
133+ </div>
134+</div>
ui/src/lib/api.ts+4
@@ -31,6 +31,10 @@ export async function searchPoints(search: string): Promise<Patp[]> {
3131 return points.map(normalizeId);
3232}
3333
34+export function getChartData(): Promise<any> {
35+ return scry<any>(`/chart-data`);
36+}
37+
3438export function getDoc(path: string): Promise<string> {
3539 return scry<string>(`/doc/${path}`);
3640}
ui/src/lib/colors.tsnew file+7
@@ -0,0 +1,7 @@
1+export const GOLD = '#fcd34d';
2+export const GOLDT2 = '#fddc71';
3+export const WHITE = '#FFF';
4+export const LGREY = '#BBB';
5+export const DGREY = '#777';
6+export const BLUE = '#1792c7';
7+export const GREEN = '#17c76a';
ui/src/lib/graph.tsnew file+213
@@ -0,0 +1,213 @@
1+import Graph from 'graphology';
2+import { clan, sein, patp2dec, patp } from 'urbit-ob';
3+import * as colors from './colors';
4+
5+const circle = 2 * Math.PI;
6+export function arrangePointsInSpirals(
7+ numPoints,
8+ {
9+ maxDistance, minDistance,
10+ startX = 0, startY = 0,
11+ maxArms = 6, maxPerArm = 45,
12+ makeSingleArmCircle = false,
13+ }): { coords: any[], numArms: number } {
14+ const numSmallEnoughArms = Math.ceil(numPoints / maxPerArm);
15+ let eRootNumArms = Math.max(1, Math.floor(Math.pow(numPoints, 1 / Math.E)));
16+ const numArms = Math.max(numSmallEnoughArms, eRootNumArms);
17+ const singleArmCircle = makeSingleArmCircle && numArms === 1;
18+ const numPerArm = numPoints / numArms;
19+ // todo refine theta range, and rename maxPerArm (misnomer)
20+ let armThetaRange = numPerArm * Math.min(Math.PI / 12, 0.25 * circle / numArms);
21+ if (singleArmCircle) {
22+ minDistance = maxDistance;
23+ armThetaRange = 2 * Math.PI;
24+ }
25+ const coords = [];
26+ let lastArm = null;
27+ let indexInArm = null;
28+ for (let i = 0; i < numPoints; i++) {
29+ const overallProgress = i / numPoints;
30+ const arm = Math.floor(numArms * overallProgress);
31+ if (lastArm === arm) {
32+ indexInArm++;
33+ } else {
34+ indexInArm = 0;
35+ }
36+ const interArmProgress = arm / numArms;
37+ // (i - (numarms * i/numpoints) * (numpoints/numarms)) / numperarm
38+ // const intraArmProgress = (i - arm * numPerArm) / numPerArm;
39+ const beginningOfNextArm = Math.floor(numPerArm * (arm + 1));
40+ const numInThisArm = beginningOfNextArm - (i - indexInArm);
41+ const intraArmProgress = indexInArm / numInThisArm;
42+ // const intraArmProgress = (numArms * overallProgress) % 1;
43+ const theta = circle * interArmProgress + armThetaRange * intraArmProgress;
44+ let baseLine = maxDistance - minDistance;
45+ let line = (minDistance + baseLine * intraArmProgress);
46+ let x = 0 + startX + line * Math.cos(theta);
47+ let y = 0 + startY + line * Math.sin(theta);
48+ coords.push({
49+ coords: [x, y],
50+ arm: singleArmCircle ? i : arm,
51+ });
52+ lastArm = arm;
53+ };
54+ return {
55+ coords,
56+ numArms,
57+ };
58+}
59+
60+export
61+
62+function populateGraph(g: Graph, rawChartData: any[]) {
63+ if (!(rawChartData instanceof Array)) return;
64+ if (!g) {
65+ g = new Graph();
66+ }
67+
68+ let sponsorEdges = [];
69+
70+ let offsetX = 0;
71+ let offsetY = 0;
72+
73+ let a = 120000000;
74+ let b = 9000000;
75+ let theta = 0;
76+
77+ rawChartData.forEach((galaxy, gi) => {
78+ offsetX = 0 + (a + b * theta) * Math.cos(theta);
79+ offsetY = 0 + (a + b * theta) * Math.sin(theta);
80+
81+ theta = 0.15 * (gi+1) * (1 - .15 * (gi / 256));
82+ const gPatp = '~' + galaxy.p;
83+ g.addNode(gPatp, {
84+ // id: gPatp,
85+ label: gPatp,
86+ size: 3,
87+ color: colors.WHITE,
88+ // color: colors.'#fcd34d',
89+ x: offsetX,
90+ y: offsetY,
91+ zIndex: 10,
92+ });
93+
94+ const stars = galaxy.c || [];
95+ const numStars = stars.length;
96+ const maxStarDistance = 60000 * 128;
97+ const starSize = 1.2 / Math.pow(numStars, 1 / 7);
98+ const {
99+ coords: starCoords,
100+ numArms: numStarArms,
101+ } = arrangePointsInSpirals(numStars,
102+ {
103+ maxDistance: maxStarDistance,
104+ minDistance: maxStarDistance / 4,
105+ startX: offsetX,
106+ startY: offsetY,
107+ makeSingleArmCircle: true,
108+ });
109+ let lastStarArm = null;
110+ let lastStarPatp = null;
111+ stars.forEach((star, si) => {
112+ const sPatp = '~' + star.p;
113+
114+ const [starX, starY] = starCoords[si].coords;
115+ const arm = starCoords[si].arm;
116+ g.addNode(sPatp, {
117+ label: sPatp,
118+ size: starSize,
119+ // color: colors.arm % 2 == 0 ? LGREY : DGREY,
120+ color: colors.GOLDT2,
121+ x: starX,
122+ y: starY,
123+ zIndex: 8,
124+ });
125+ if (lastStarArm == arm) {
126+ g.addEdge(sPatp, lastStarPatp, {
127+ color: colors.LGREY,
128+ size: 0.1,
129+ zIndex: 7,
130+ });
131+ } else {
132+ g.addEdge(sPatp, gPatp, {
133+ color: colors.LGREY,
134+ size: 0.2,
135+ zIndex: 9,
136+ });
137+ }
138+ lastStarArm = arm;
139+ lastStarPatp = sPatp;
140+ if (star.s) {
141+ sponsorEdges.push([sPatp, '~' + star.s, {
142+ color: colors.GREEN,
143+ size: 0.2,
144+ zIndex: 11,
145+ }]);
146+ }
147+
148+ const planets = star.c || [];
149+ const numPlanets = planets.length;
150+ const maxPlanetDistance = starSize * 1.6 * 4000 * 64;
151+ const {
152+ coords: planetCoords,
153+ numArms: numPlanetArms,
154+ } = arrangePointsInSpirals(numPlanets,
155+ {
156+ maxDistance: maxPlanetDistance,
157+ minDistance: maxPlanetDistance / 4,
158+ startX: starX,
159+ startY: starY,
160+ maxArms: 30,
161+ makeSingleArmCircle: true,
162+ });
163+ let lastPlanetArm = null;
164+ let lastPlanetPatp = null;
165+ planets.forEach((planet, pi) => {
166+ const pPatp = '~' + planet.p;
167+ // const circle = 2 * Math.PI;
168+ // let line = 16000 * 64;
169+ // let planetX = 0 + starX + Math.cos(circle * pi / numPlanets) * line;
170+ // let planetY = 0 + starY + Math.sin(circle * pi / numPlanets) * line;
171+
172+ const [planetX, planetY] = planetCoords[pi].coords;
173+ const arm = planetCoords[pi].arm;
174+ g.addNode(pPatp, {
175+ label: pPatp,
176+ size: .15,
177+ color: planet.d == 'l2' ? colors.GREEN : colors.BLUE,
178+ x: planetX,
179+ y: planetY,
180+ zIndex: 6,
181+ });
182+ if (lastPlanetArm == arm) {
183+ g.addEdge(pPatp, lastPlanetPatp, {
184+ color: colors.DGREY,
185+ size: 0.05,
186+ zIndex: 5,
187+ });
188+ } else {
189+ if (numPlanetArms == 1) {
190+ g.addEdge(pPatp, sPatp, {
191+ color: colors.DGREY,
192+ size: 0.05,
193+ zIndex: 5,
194+ });
195+ }
196+ }
197+ if (planet.s) {
198+ sponsorEdges.push([pPatp, '~' + planet.s, {
199+ color: colors.GREEN,
200+ size: 0.05,
201+ zIndex: 11,
202+ }]);
203+ }
204+ lastPlanetArm = arm;
205+ lastPlanetPatp = pPatp;
206+ });
207+ });
208+ });
209+ sponsorEdges.forEach(([point, sponsor, attrs]) => {
210+ // g.addEdge(point, sponsor, attrs);
211+ });
212+ return g;
213+}
ui/src/routes/Home.svelte+25−20
@@ -7,6 +7,7 @@
77 import SearchBar from "../components/SearchBar.svelte";
88 import SearchResults from "../components/SearchResults.svelte";
99 import ShipView from '../components/ShipView.svelte';
10+ import StarChart from '../components/StarChart.svelte';
1011 import ValidationProblems from '../components/ValidationProblems.svelte';
1112 import HomeScreenTiles from '../components/HomeScreenTiles.svelte';
1213 export let params: any = {};
@@ -70,8 +71,8 @@
7071 }
7172 }
7273 // $: urlChangedSinceAnalysisPatp = (urlChanged - analysisPatpChanged) > 1000;
73− function doStuff() {
74− if (search !== urlSearch) {
74+ function autoNav() {
75+ if (params.mode !== 'chart' && search !== urlSearch) {
7576 if (urlChangedSinceAnalysisPatp) {
7677 search = urlSearch;
7778 } else if (analysis.search === search) {
@@ -84,18 +85,20 @@
8485 }
8586 }
8687 }
87− $: urlSearch, analysis, doStuff();
88+ $: urlSearch, analysis, autoNav();
8889 $: {
89− if (analysis.search === search) {
90− let properMode = null;
90+ let properMode = params.mode;
91+ if (properMode === 'chart') {
92+ // nice
93+ } else if (analysis.search === search) {
9194 if (analysis.patpIsValid) {
9295 properMode = 'ship';
9396 } else if (analysis.search.length > 0) {
9497 properMode = 'search';
9598 }
96− if (params.mode !== properMode) {
97− navToMode(properMode, false);
98− }
99+ }
100+ if (params.mode !== properMode) {
101+ navToMode(properMode, false);
99102 }
100103 }
101104
@@ -108,6 +111,8 @@
108111 case 'search':
109112 $pageName = 'Search';
110113 break;
114+ case 'chart':
115+ $pageName = 'Star Chart';
111116 default:
112117 $pageName = null;
113118 }
@@ -117,19 +122,19 @@
117122
118123</script>
119124
120−<div class="m-4 flex flex-col items-center space-y-8 2xs:m-8">
125+<div class="p-4 min-h-full flex flex-col items-center space-y-8 2xs:p-8">
121126 <SearchBar bind:analysis bind:search />
122127 <!-- <p class="bg-white">navigating</p> -->
123− {#if analysis.patpIsValid}
124− <!-- {#if analysis.search === urlSearch} -->
125− <ShipView patp={analysis.patp} />
126− <!-- {/if} -->
127− {:else if (analysis.queryIsValid)}
128− <SearchResults query={analysis.query} search={analysis.search} />
129− {:else if (analysis.search.length > 0)}
130− <ValidationProblems problems={analysis.queryProblems} />
131− {:else}
132− <HomeScreenTiles />
133− {/if}
128+ {#if params.mode == "chart"}
129+ <StarChart patp={analysis.patpIsValid ? analysis.patp : null} />
130+ {:else if analysis.patpIsValid}
131+ <ShipView patp={analysis.patp} />
132+ {:else if (analysis.queryIsValid)}
133+ <SearchResults query={analysis.query} search={analysis.search} />
134+ {:else if (analysis.search.length > 0)}
135+ <ValidationProblems problems={analysis.queryProblems} />
136+ {:else}
137+ <HomeScreenTiles />
138+ {/if}
134139 <!-- {/if} -->
135140</div>
ui/src/styles/tailwind-output.css+95−11
@@ -424,6 +424,51 @@ Ensure the default browser behavior of the `hidden` attribute.
424424 --tw-backdrop-saturate: ;
425425 --tw-backdrop-sepia: ;
426426}
427+.container {
428+ width: 100%;
429+}
430+@media (min-width: 360px) {
431+
432+ .container {
433+ max-width: 360px;
434+ }
435+}
436+@media (min-width: 475px) {
437+
438+ .container {
439+ max-width: 475px;
440+ }
441+}
442+@media (min-width: 640px) {
443+
444+ .container {
445+ max-width: 640px;
446+ }
447+}
448+@media (min-width: 768px) {
449+
450+ .container {
451+ max-width: 768px;
452+ }
453+}
454+@media (min-width: 1024px) {
455+
456+ .container {
457+ max-width: 1024px;
458+ }
459+}
460+@media (min-width: 1280px) {
461+
462+ .container {
463+ max-width: 1280px;
464+ }
465+}
466+@media (min-width: 1536px) {
467+
468+ .container {
469+ max-width: 1536px;
470+ }
471+}
427472.pointer-events-none {
428473 pointer-events: none;
429474}
@@ -592,6 +637,9 @@ Ensure the default browser behavior of the `hidden` attribute.
592637.h-12 {
593638 height: 3rem;
594639}
640+.h-3 {
641+ height: 0.75rem;
642+}
595643.h-\[100vh\] {
596644 height: 100vh;
597645}
@@ -607,6 +655,15 @@ Ensure the default browser behavior of the `hidden` attribute.
607655.h-4 {
608656 height: 1rem;
609657}
658+.h-24 {
659+ height: 6rem;
660+}
661+.min-h-full {
662+ min-height: 100%;
663+}
664+.min-h-0 {
665+ min-height: 0px;
666+}
610667.w-full {
611668 width: 100%;
612669}
@@ -616,6 +673,9 @@ Ensure the default browser behavior of the `hidden` attribute.
616673.w-12 {
617674 width: 3rem;
618675}
676+.w-3 {
677+ width: 0.75rem;
678+}
619679.w-\[100vw\] {
620680 width: 100vw;
621681}
@@ -628,6 +688,9 @@ Ensure the default browser behavior of the `hidden` attribute.
628688.w-4 {
629689 width: 1rem;
630690}
691+.w-6 {
692+ width: 1.5rem;
693+}
631694.min-w-0 {
632695 min-width: 0px;
633696}
@@ -711,6 +774,12 @@ Ensure the default browser behavior of the `hidden` attribute.
711774.gap-1 {
712775 gap: 0.25rem;
713776}
777+.gap-4 {
778+ gap: 1rem;
779+}
780+.gap-2 {
781+ gap: 0.5rem;
782+}
714783.gap-x-2 {
715784 -moz-column-gap: 0.5rem;
716785 column-gap: 0.5rem;
@@ -818,14 +887,17 @@ Ensure the default browser behavior of the `hidden` attribute.
818887.border-l {
819888 border-left-width: 1px;
820889}
821−.border-navy {
822− --tw-border-opacity: 1;
823− border-color: rgb(31 36 60 / var(--tw-border-opacity));
890+.border-r {
891+ border-right-width: 1px;
824892}
825893.border-gold {
826894 --tw-border-opacity: 1;
827895 border-color: rgb(252 211 77 / var(--tw-border-opacity));
828896}
897+.border-navy {
898+ --tw-border-opacity: 1;
899+ border-color: rgb(31 36 60 / var(--tw-border-opacity));
900+}
829901.border-gray-300 {
830902 --tw-border-opacity: 1;
831903 border-color: rgb(209 213 219 / var(--tw-border-opacity));
@@ -858,6 +930,14 @@ Ensure the default browser behavior of the `hidden` attribute.
858930 --tw-bg-opacity: 1;
859931 background-color: rgb(0 0 0 / var(--tw-bg-opacity));
860932}
933+.bg-l1-blue {
934+ --tw-bg-opacity: 1;
935+ background-color: rgb(23 146 199 / var(--tw-bg-opacity));
936+}
937+.bg-l2-green {
938+ --tw-bg-opacity: 1;
939+ background-color: rgb(23 199 106 / var(--tw-bg-opacity));
940+}
861941.bg-orange-gold {
862942 --tw-bg-opacity: 1;
863943 background-color: rgb(247 211 146 / var(--tw-bg-opacity));
@@ -991,6 +1071,10 @@ Ensure the default browser behavior of the `hidden` attribute.
9911071 padding-left: 2rem;
9921072 padding-right: 2rem;
9931073}
1074+.py-1 {
1075+ padding-top: 0.25rem;
1076+ padding-bottom: 0.25rem;
1077+}
9941078.px-0\.5 {
9951079 padding-left: 0.125rem;
9961080 padding-right: 0.125rem;
@@ -1003,10 +1087,6 @@ Ensure the default browser behavior of the `hidden` attribute.
10031087 padding-left: 0.75rem;
10041088 padding-right: 0.75rem;
10051089}
1006−.py-1 {
1007− padding-top: 0.25rem;
1008− padding-bottom: 0.25rem;
1009−}
10101090.px-1 {
10111091 padding-left: 0.25rem;
10121092 padding-right: 0.25rem;
@@ -1095,6 +1175,10 @@ Ensure the default browser behavior of the `hidden` attribute.
10951175.leading-3 {
10961176 line-height: .75rem;
10971177}
1178+.text-white {
1179+ --tw-text-opacity: 1;
1180+ color: rgb(255 255 255 / var(--tw-text-opacity));
1181+}
10981182.text-gray-700 {
10991183 --tw-text-opacity: 1;
11001184 color: rgb(55 65 81 / var(--tw-text-opacity));
@@ -1123,10 +1207,6 @@ Ensure the default browser behavior of the `hidden` attribute.
11231207 --tw-text-opacity: 1;
11241208 color: rgb(21 128 61 / var(--tw-text-opacity));
11251209}
1126−.text-white {
1127− --tw-text-opacity: 1;
1128− color: rgb(255 255 255 / var(--tw-text-opacity));
1129−}
11301210.text-gray-400 {
11311211 --tw-text-opacity: 1;
11321212 color: rgb(156 163 175 / var(--tw-text-opacity));
@@ -1181,6 +1261,10 @@ Ensure the default browser behavior of the `hidden` attribute.
11811261 margin-right: 2rem;
11821262 }
11831263
1264+ .\32xs\:p-8 {
1265+ padding: 2rem;
1266+ }
1267+
11841268 .\32xs\:py-8 {
11851269 padding-top: 2rem;
11861270 padding-bottom: 2rem;
ui/tailwind.config.cjs+3−1
@@ -11,6 +11,9 @@ module.exports = {
1111 colors: {
1212 navy: '#1f243c',
1313 gold: '#fcd34d',
14+ 'orange-gold': '#f7d392',
15+ 'l1-blue': '#1792c7',
16+ 'l2-green': '#17c76a',
1417 'gold-s1': '#e3be45',
1518 'gold-s2': '#caa93e',
1619 'gold-s3': '#b09436',
@@ -29,7 +32,6 @@ module.exports = {
2932 // 'gold-t7': '4c3f17',
3033 'gold-t8': '#fef6db',
3134 // 'gold-t9': '191508',
32− 'orange-gold': '#f7d392',
3335 }
3436 },
3537 screens: {