1import Graph from 'graphology';
2import * as colors from './colors';
4const example = '[0[3216291:t;9219219;4608:s[156546;1612196;219621:~;321965149:4608;]1280[4289594624;4278191360;426672256;4260168960;4260824320;4246668544;4246144256;4253746432;4235396352;4230415616;4225697024;4227007744;4219667712;4222616832;4224648448;4214490368;4215997696;4219143424;4237296896;]]]'
6export interface ChartPoint {
8 dominion: ('l1' | 'l2' | 'spawn'),
10 pickedChildren?: boolean,
11 pickedSponsees?: boolean,
14 children?: ChartPoint[],
15 sponsees?: ChartPoint[],
19 children: ChartPoint[],
20 sponsees: ChartPoint[],
23class ChartDataParser {
26 constructor(filter: Set<string>) {
30 parseMetaString(metaString: string): ChartPoint {
31 let idString, subMetaString = '';
32 const colIndex = metaString.indexOf(':');
33 if (colIndex === -1) {
34 idString = metaString;
36 idString = metaString.slice(0, colIndex);
37 subMetaString = metaString.slice(colIndex + 1);
39 const patp = '~' + idString;
40 const meta: ChartPoint = {
42 picked: !this.filter ? true : (this.filter.has(patp)),
46 const firstChar = subMetaString[0];
47 if (firstChar === 't') {
49 subMetaString = subMetaString.slice(1);
50 } else if (firstChar === 's') {
51 meta.dominion = 'spawn';
52 subMetaString = subMetaString.slice(1);
55 if (subMetaString[0] === '~') {
58 let [sponsor, parent] = subMetaString.slice(1).split('!');
59 meta.sponsor = '~' + sponsor;
60 meta.parent = '~' + parent;
67 extractMeta(data: string): [ChartPoint, string] {
68 const index = data.search(/[[;]/);
70 throw new Error(`No point meta present in ${data}`);
72 const metaString = data.slice(0, index);
73 data = data.slice(index);
74 return [this.parseMetaString(metaString), data];
77 extractChildren(data: string, sup: string = undefined): [ChartSubs, string] {
83 if (data[0] !== '[') throw new Error('Invalid children in data: ' + data.slice(0, 20));
90 while (!endFound && data.length && counter < 20000) {
91 if (data[0] === ']') {
96 [sub, data] = this.extractChartPoint(data);
97 if (!sub.parent || sub.parent === sup)
99 if (sub.sponsor === undefined || sub.sponsor === sup)
104 return [{ children, sponsees }, data];
107 extractChartPoint(data: string): [ChartPoint, string] {
109 [point, data] = this.extractMeta(data);
110 [subs, data] = this.extractChildren(data, point.patp);
115 return [point, data];
118 linkChartData(points: ChartPoint[], store: any): ChartPoint[] {
119 if (!points) return points;
120 return points.map(point => {
121 if (!store[point.patp]) return point;
122 if (!point.children?.length) {
123 point.children = this.linkChartData(store[point.patp].children, store);
124 let picked = point.children.filter(child => child.picked || child.pickedChildren);
125 point.pickedChildren = picked.length > 0;
127 if (!point.sponsees?.length) {
128 point.sponsees = this.linkChartData(store[point.patp].sponsees, store);
129 let picked = point.sponsees.filter(child => child.picked || child.pickedSponsees);
130 point.pickedSponsees = picked.length > 0;
136 parse(str: string): ChartSubs {
137 if (!str) return { children: [], sponsees: [] };
138 let [{ children: baseChildren }, _] = this.extractChildren(str, undefined);
140 baseChildren.forEach(child => {
141 store[child.patp] = {
142 children: child.children,
143 sponsees: child.sponsees,
146 let base: ChartPoint = {
152 let points = this.linkChartData([base], store);
154 children: points[0].children || [],
155 sponsees: points[0].sponsees || [],
160export function parseChartData(data: string, filter: Set<string>): ChartSubs {
161 let start = Date.now();
162 let parser = new ChartDataParser(filter);
163 let parsed = parser.parse(data);
164 // console.log(`finished parsing chart data to js object in ${Date.now() - start} ms`);
168const circle = 2 * Math.PI;
169function chordLength(radius, radians) {
170 return 2 * radius * Math.sin(radians / 2);
172function distancePolar(r1, th1, r2, th2) {
173 return Math.sqrt((r1 * r1) + (r2 * r2) - (2 * r1 * r2 * Math.cos(th2 - th1)));
175export function arrangePointsInSpirals(
178 maxDistance, minDistance,
179 startX = 0, startY = 0,
180 maxArms = 6, maxPerArm = 45,
181 makeSingleArmCircle = false,
182 }): { coords: any[], numArms: number, minDistanceBetween: number } {
183 const numSmallEnoughArms = Math.ceil(numPoints / maxPerArm);
184 let eRootNumArms = Math.max(1, Math.floor(Math.pow(numPoints, 1 / Math.E)));
185 const numArms = Math.max(numSmallEnoughArms, eRootNumArms);
186 const singleArmCircle = makeSingleArmCircle && numArms === 1;
187 const numPerArm = numPoints / numArms;
188 let armThetaRange = numPerArm * Math.min(Math.PI / 12, 0.25 * circle / numArms);
189 if (singleArmCircle) {
190 minDistance = maxDistance;
191 armThetaRange = circle;
193 const distanceRange = maxDistance - minDistance;
194 const pointWeight = 1 / numPerArm;
195 let minDistanceBetween = distancePolar(minDistance, 0,
196 minDistance + distanceRange * pointWeight, armThetaRange * pointWeight);
197 if (numPoints <= 3) minDistanceBetween = maxDistance * 0.75;
200 let indexInArm = null;
201 for (let i = 0; i < numPoints; i++) {
202 const overallProgress = i / numPoints;
203 const arm = Math.floor(numArms * overallProgress);
204 if (lastArm === arm) {
209 const interArmProgress = arm / numArms;
210 const beginningOfNextArm = Math.floor(numPerArm * (arm + 1));
211 const numInThisArm = beginningOfNextArm - (i - indexInArm);
212 const intraArmProgress = indexInArm / numInThisArm;
213 const theta = circle * interArmProgress + armThetaRange * intraArmProgress;
214 let line = (minDistance + distanceRange * intraArmProgress);
215 let x = 0 + startX + line * Math.cos(theta);
216 let y = 0 + startY + line * Math.sin(theta);
219 arm: singleArmCircle ? i : arm,
229type childKey = ('sponsees' | 'children');
230export function populateGraph(g: Graph, chartSubs: ChartSubs, subsKey: childKey, callback?): Graph {
231 let pickedSubsKey = subsKey == 'children' ? 'pickedChildren' : 'pickedSponsees';
232 function pickedColor(point: ChartPoint, normal: string) {
233 return point.picked ? normal : (point[pickedSubsKey] ? colors.SUB_PICKED : colors.NOT_PICKED);
235 function pickedArmColor(point: ChartPoint, normal: string) {
236 return point[pickedSubsKey] ? normal : colors.NOT_PICKED;
238 let points = chartSubs[subsKey];
239 if (!(points instanceof Array)) return;
246 let sponsorEdges = [];
255 points.forEach((galaxy, gi) => {
256 offsetX = 0 + (a + b * theta) * Math.cos(theta);
257 offsetY = 0 + (a + b * theta) * Math.sin(theta);
259 theta = 0.15 * (gi+1) * (1 - .15 * (gi / 256));
260 const gPatp = galaxy.patp;
261 const itsYou = gPatp === '~' + window.ship;
265 color: itsYou ? colors.YOU : pickedColor(galaxy, colors.WHITE),
271 const stars = galaxy[subsKey] || [];
272 const numStars = stars.length;
273 const maxStarDistance = 60000 * 128;
274 const minStarDistance = maxStarDistance / 4;
275 const starSize = 1.2 / Math.pow(numStars, 1 / 7);
278 numArms: numStarArms,
279 minDistanceBetween: minDistanceBetweenStars,
280 } = arrangePointsInSpirals(numStars,
282 maxDistance: maxStarDistance,
283 minDistance: minStarDistance,
286 makeSingleArmCircle: true,
288 let lastStarArm = null;
289 let lastStarPatp = null;
290 let GSarmColor = pickedArmColor(galaxy, colors.LGREY)
291 stars.forEach((star, si) => {
292 const sPatp = star.patp;
294 const [starX, starY] = starCoords[si].coords;
295 const arm = starCoords[si].arm;
297 const itsYou = sPatp === '~' + window.ship;
299 g.setNodeAttribute(gPatp, 'color', colors.YOUR_GAL);
305 color: itsYou ? colors.YOU : pickedColor(star, colors.GOLDT2),
310 if (lastStarArm == arm) {
311 g.addEdge(sPatp, lastStarPatp, {
317 g.addEdge(sPatp, gPatp, {
324 lastStarPatp = sPatp;
326 sponsorEdges.push([sPatp, star.sponsor, {
333 const planets = star[subsKey] || [];
334 const numPlanets = planets.length;
335 // const maxPlanetDistance = starSize * 1.6 * 4000 * 64;
336 const maxPlanetDistance = minDistanceBetweenStars * 0.45;
337 const minPlanetDistance = maxPlanetDistance / 4;
339 coords: planetCoords,
340 numArms: numPlanetArms,
341 minDistanceBetween: minDistanceBetweenPlanets,
342 } = arrangePointsInSpirals(numPlanets,
344 maxDistance: maxPlanetDistance,
345 minDistance: minPlanetDistance,
349 makeSingleArmCircle: true,
351 let lastPlanetArm = null;
352 let lastPlanetPatp = null;
353 let SParmColor = pickedArmColor(star, colors.DGREY)
354 planets.forEach((planet, pi) => {
355 const pPatp = planet.patp;
356 const [planetX, planetY] = planetCoords[pi].coords;
357 const arm = planetCoords[pi].arm;
358 const planetSize = Math.sqrt(Math.min(minPlanetDistance, minDistanceBetweenPlanets)) / 1300;
359 const planetEdgeSize = planetSize / 3;
360 const itsYou = pPatp === '~' + window.ship;
362 g.setNodeAttribute(sPatp, 'color', colors.YOUR_STAR);
363 g.setNodeAttribute(gPatp, 'color', colors.YOUR_GAL);
369 color: itsYou ? colors.YOU : pickedColor(planet, planet.dominion == 'l2' ? colors.GREEN : colors.BLUE),
374 if (lastPlanetArm == arm) {
375 g.addEdge(pPatp, lastPlanetPatp, {
377 size: planetEdgeSize,
381 if (numPlanetArms == 1) {
382 g.addEdge(pPatp, sPatp, {
384 size: planetEdgeSize,
389 if (planet.sponsor) {
390 sponsorEdges.push([pPatp, planet.sponsor, {
392 size: planetEdgeSize,
397 lastPlanetPatp = pPatp;
399 let msg = `Couldn't add ${pPatp} to the graph`;
400 console.error(msg, e);
401 alertMe(msg + '\n' + e.message);
405 let msg = `Couldn't add ${sPatp} to the graph`;
406 console.error(msg, e);
407 alertMe(msg + '\n' + e.message);
411 sponsorEdges.forEach(([point, sponsor, attrs]) => {
412 // g.addEdge(point, sponsor, attrs);
415 setTimeout(callback, 0);
420function alertMe(msg: string) {
421 alert('Encountered an error, please tell ~midlev-mindyr: ' + msg);
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list