celestial-chart 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/svg.js ADDED
@@ -0,0 +1,879 @@
1
+ import * as d3 from "./d3.js";
2
+
3
+ import { bvcolor, formats, projections, settings } from "./config.js";
4
+ import { getData, getGridValues, getMwbackground, getPlanet } from "./get.js";
5
+ import { Celestial } from "./core.js";
6
+ import { poles } from "./projection.js";
7
+ import { euler, getAngles, halfπ, transformDeg } from "./transform.js";
8
+ import { Round, attrs, taskQueue, functor, has, isArray, loadJson } from "./util.js";
9
+
10
+ // SVG export of one map. It receives the instance so that it works from that
11
+ // map's own configuration, container and name tables.
12
+ function exportSVG(sky, done, fname) {
13
+ var doc = d3.select("body").append("div").attr("id", "d3-celestial-svg").attr("style", "display: none"),
14
+ svg = d3.select("#d3-celestial-svg").append("svg"), //.attr("style", "display: none"),
15
+ m = Celestial.metrics(),
16
+ cfg = sky.cfg,
17
+ path = cfg.datapath,
18
+ proj = projections[cfg.projection],
19
+ rotation = getAngles(cfg.center),
20
+ center = [-rotation[0], -rotation[1]],
21
+ scale0 = proj.scale * m.width/1024,
22
+ projection = Celestial.projection(cfg.projection).rotate(rotation).translate([m.width/2, m.height/2]).scale([m.scale]),
23
+ adapt = cfg.adaptable ? Math.sqrt(m.scale/scale0) : 1,
24
+ culture = (cfg.culture !== "" && cfg.culture !== "iau") ? cfg.culture : "",
25
+ circle, id;
26
+
27
+ svg.selectAll("*").remove();
28
+
29
+ var defs = svg.append("defs");
30
+
31
+ if (proj.clip) {
32
+ projection.clipAngle(90);
33
+ }
34
+ // The output has a fixed size, so anything outside the page is invisible
35
+ // anyway — but without clipping, non-finite coordinates end up in the paths.
36
+ // Mercator's background circle, for instance, includes the poles, where the
37
+ // projection goes to plus or minus infinity, and `Infinity` is invalid in an
38
+ // SVG `d` attribute: the browser discards the whole path. clipExtent is a
39
+ // post-clip, so it coexists with clipAngle.
40
+ projection.clipExtent([[0, 0], [m.width, m.height]]);
41
+ circle = d3.geoCircle().radius(179.95).center(center);
42
+
43
+ svg.attr("width", m.width).attr("height", m.height);
44
+ // .attr("viewBox", " 0 0 " + (m.width) + " " + (m.height));
45
+
46
+ var groupNames = ['background', 'milkyWay', 'milkyWayBg', 'gridLines', 'constBoundaries',
47
+ 'planesequatorial', 'planesecliptic', 'planesgalactic', 'planessupergalactic',
48
+ 'constLines', 'mapBorder','stars', 'dsos', 'planets', 'gridvaluesLon', 'gridvaluesLat',
49
+ 'constNames', 'starDesignations', 'starNames', 'dsoNames', 'planetNames', 'horizon', 'daylight'],
50
+ groups = {}, styles = {};
51
+
52
+ for (var i=0; i<groupNames.length; i++) {
53
+ // inkscape:groupmode="layer", inkscape:label="Ebene 1"
54
+ groups[groupNames[i]] = attrs(svg.append('g'),
55
+ {"id": groupNames[i], ":inkscape:groupmode": "layer", ":inkscape:label": groupNames[i]});
56
+ styles[groupNames[i]] = {};
57
+ }
58
+
59
+ var graticule = d3.geoGraticule().stepMinor([15,10]);
60
+
61
+ var map = d3.geoPath().projection(projection);
62
+
63
+ var q = taskQueue(2);
64
+
65
+ groups.background.append("path").datum(circle).attr("class", "background").attr("d", map);
66
+ styles.background.fill = cfg.background.fill;
67
+
68
+ if (cfg.lines.graticule.show) {
69
+ if (cfg.transform === "equatorial") {
70
+ groups.gridLines.append("path").datum(graticule)
71
+ .attr("class", "gridLines")
72
+ .attr("d", map);
73
+ styles.gridLines = svgStyle(cfg.lines.graticule);
74
+ } else {
75
+ Celestial.graticule(groups.gridLines, map, cfg.transform);
76
+ styles.gridLines = svgStyle(cfg.lines.graticule);
77
+ }
78
+ if (has(cfg.lines.graticule, "lon") && cfg.lines.graticule.lon.pos.length > 0) {
79
+ var jlon = {type: "FeatureCollection", features: getGridValues("lon", cfg.lines.graticule.lon.pos, sky)};
80
+ groups.gridvaluesLon.selectAll(".gridvalues_lon")
81
+ .data(jlon.features)
82
+ .enter().append("text")
83
+ .attr("transform", function(d, i) { return point(d.geometry.coordinates); })
84
+ .text( function(d) { return d.properties.value; } )
85
+ .call(function (v) { attrs(v, {dy: ".5em", dx: "-.75em", "class": "gridvaluesLon"}); });
86
+ styles.gridvaluesLon = svgTextStyle(cfg.lines.graticule.lon);
87
+ }
88
+ if (has(cfg.lines.graticule, "lat") && cfg.lines.graticule.lat.pos.length > 0) {
89
+ var jlat = {type: "FeatureCollection", features: getGridValues("lat", cfg.lines.graticule.lat.pos, sky)};
90
+ groups.gridvaluesLat.selectAll(".gridvalues_lat")
91
+ .data(jlat.features)
92
+ .enter().append("text")
93
+ .attr("transform", function(d, i) { return point(d.geometry.coordinates); })
94
+ .text( function(d) { return d.properties.value; } )
95
+ .call(function (v) { attrs(v, {dy: "-.5em", dx: "-.75em", "class": "gridvaluesLat"}); });
96
+ styles.gridvaluesLat = svgTextStyle(cfg.lines.graticule.lat);
97
+ }
98
+ }
99
+
100
+ //Celestial planes
101
+ for (var key in cfg.lines) {
102
+ if (has(cfg.lines, key) && key != "graticule" && cfg.lines[key].show !== false) {
103
+ id = "planes" + key;
104
+ groups[id].append("path")
105
+ .datum(d3.geoCircle().radius(90).center(poles[key]) )
106
+ .attr("class", id)
107
+ .attr("d", map);
108
+ styles[id] = svgStyle(cfg.lines[key]);
109
+ }
110
+ }
111
+
112
+ //Milky way outline
113
+ if (cfg.mw.show) {
114
+ q.defer(function(callback) {
115
+ loadJson(path + "mw.json", function(error, json) {
116
+ if (error) callback(error);
117
+ var mw = getData(json, cfg.transform);
118
+ var mw_back = getMwbackground(mw);
119
+
120
+ groups.milkyWay.selectAll(".mway")
121
+ .data(mw.features)
122
+ .enter().append("path")
123
+ .attr("class", "milkyWay")
124
+ .attr("d", map);
125
+ styles.milkyWay = svgStyle(cfg.mw.style);
126
+
127
+ if (!has(cfg.background, "opacity") || cfg.background.opacity > 0.95) {
128
+ groups.milkyWayBg.selectAll(".mwaybg")
129
+ .data(mw_back.features)
130
+ .enter().append("path")
131
+ .attr("class", "milkyWayBg")
132
+ .attr("d", map);
133
+ styles.milkyWayBg = {"fill": cfg.background.fill,
134
+ "fill-opacity": cfg.background.opacity };
135
+ }
136
+ callback(null);
137
+ });
138
+ });
139
+ }
140
+
141
+ //Constellation boundaries
142
+ if (cfg.constellations.bounds) {
143
+ q.defer(function(callback) {
144
+ loadJson(path + filename("constellations", "borders"), function(error, json) {
145
+ if (error) callback(error);
146
+
147
+ var conb = getData(json, cfg.transform);
148
+ if (Celestial.constellation) {
149
+ var re = new RegExp("\\b" + Celestial.constellation + "\\b");
150
+ }
151
+
152
+ groups.constBoundaries.selectAll(".bounds")
153
+ .data(conb.features)
154
+ .enter().append("path")
155
+ .attr("class", function(d) { return (Celestial.constellation && d.ids.search(re) !== -1) ? "constBoundariesSel" : "constBoundaries"; })
156
+ .attr("d", map);
157
+
158
+ styles.constBoundaries = svgStyle(cfg.constellations.boundStyle);
159
+ styles.constBoundariesSel = {"fill": "none",
160
+ "stroke": cfg.constellations.boundStyle.stroke,
161
+ "stroke-width": cfg.constellations.boundStyle.width * 1.5,
162
+ "stroke-opacity": 1,
163
+ "stroke-dasharray": "none"};
164
+
165
+ callback(null);
166
+ });
167
+ });
168
+ }
169
+
170
+ //Constellation lines
171
+ if (cfg.constellations.lines) {
172
+ q.defer(function(callback) {
173
+ loadJson(path + filename("constellations", "lines"), function(error, json) {
174
+ if (error) callback(error);
175
+
176
+ var conl = getData(json, cfg.transform);
177
+ groups.constLines.selectAll(".lines")
178
+ .data(conl.features)
179
+ .enter().append("path")
180
+ .attr("class", function(d) { return "constLines" + d.properties.rank; })
181
+ .attr("d", map);
182
+
183
+ var dasharray = has(cfg.constellations.lineStyle, "dash") ? cfg.constellations.lineStyle.dash.join(" ") : "none";
184
+
185
+ styles.constLines1 = {"fill": "none", "stroke": cfg.constellations.lineStyle.stroke[0],
186
+ "stroke-width": cfg.constellations.lineStyle.width[0],
187
+ "stroke-opacity": cfg.constellations.lineStyle.opacity[0],
188
+ "stroke-dasharray": dasharray};
189
+ styles.constLines2 = {"fill": "none", "stroke": cfg.constellations.lineStyle.stroke[1],
190
+ "stroke-width": cfg.constellations.lineStyle.width[1],
191
+ "stroke-opacity": cfg.constellations.lineStyle.opacity[1],
192
+ "stroke-dasharray": dasharray};
193
+ styles.constLines3 = {"fill": "none", "stroke": cfg.constellations.lineStyle.stroke[2],
194
+ "stroke-width": cfg.constellations.lineStyle.width[2],
195
+ "stroke-opacity": cfg.constellations.lineStyle.opacity[2],
196
+ "stroke-dasharray": dasharray};
197
+ callback(null);
198
+ });
199
+ });
200
+ }
201
+
202
+ // Map border
203
+ q.defer(function(callback) {
204
+ var rot = projection.rotate();
205
+ projection.rotate([0,0,0]);
206
+ groups.mapBorder.append("path")
207
+ .datum(graticule.outline)
208
+ .attr("class", "mapBorder")
209
+ .attr("d", map);
210
+
211
+ styles.mapBorder = {"fill": "none", "stroke": cfg.background.stroke, "stroke-width": cfg.background.width, "stroke-opacity": 1, "stroke-dasharray": "none" };
212
+
213
+ projection.rotate(rot);
214
+ callback(null);
215
+ });
216
+
217
+ //Constellation names or designation
218
+ if (cfg.constellations.names) {
219
+ q.defer(function(callback) {
220
+ loadJson(path + filename("constellations"), function(error, json) {
221
+ if (error) callback(error);
222
+
223
+ var conn = getData(json, cfg.transform);
224
+ groups.constNames.selectAll(".constnames")
225
+ .data(conn.features.filter( function(d) {
226
+ return clip(d.geometry.coordinates) === 1;
227
+ }))
228
+ .enter().append("text")
229
+ .attr("class", function(d) { return "constNames" + d.properties.rank; })
230
+ .attr("transform", function(d, i) { return point(d.geometry.coordinates); })
231
+ .text( function(d) { return constName(d); } );
232
+
233
+ styles.constNames1 = {"fill": cfg.constellations.nameStyle.fill[0],
234
+ "fill-opacity": cfg.constellations.nameStyle.opacity[0],
235
+ "font": cfg.constellations.nameStyle.font[0],
236
+ "text-anchor": svgAlign(cfg.constellations.nameStyle.align)};
237
+ styles.constNames2 = {"fill": cfg.constellations.nameStyle.fill[1],
238
+ "fill-opacity": cfg.constellations.nameStyle.opacity[1],
239
+ "font": cfg.constellations.nameStyle.font[1],
240
+ "text-anchor": svgAlign(cfg.constellations.nameStyle.align)};
241
+ styles.constNames3 = {"fill": cfg.constellations.nameStyle.fill[2],
242
+ "fill-opacity": cfg.constellations.nameStyle.opacity[2],
243
+ "font": cfg.constellations.nameStyle.font[2],
244
+ "text-anchor": svgAlign(cfg.constellations.nameStyle.align)};
245
+ callback(null);
246
+ });
247
+ });
248
+ }
249
+
250
+ //Stars
251
+ if (cfg.stars.show) {
252
+ q.defer(function(callback) {
253
+ loadJson(path + cfg.stars.data, function(error, json) {
254
+ if (error) callback(error);
255
+
256
+ var cons = getData(json, cfg.transform);
257
+
258
+ groups.stars.selectAll(".stars")
259
+ .data(cons.features.filter( function(d) {
260
+ return d.properties.mag <= cfg.stars.limit;
261
+ }))
262
+ .enter().append("path")
263
+ .attr("class", function(d) { return "stars" + starColor(d.properties.bv); })
264
+ .attr("d", map.pointRadius( function(d) {
265
+ return d.properties ? starSize(d.properties.mag) : 1;
266
+ }));
267
+
268
+ styles.stars = svgStyle(cfg.stars.style);
269
+ // The direction of the domain depends on how the scale was built
270
+ // (descending in v3, ascending in v7) — the loop must not depend on it.
271
+ var range = bvcolor.domain(),
272
+ bvMin = Math.min(range[0], range[1]),
273
+ bvMax = Math.max(range[0], range[1]);
274
+ for (i=Round(bvMin,1); i<=Round(bvMax,1); i+=0.1) {
275
+ styles["stars" + Math.round(i*10).toString()] = {"fill": bvcolor(i)};
276
+ }
277
+
278
+ if (cfg.stars.designation) {
279
+ groups.starDesignations.selectAll(".stardesigs")
280
+ .data(cons.features.filter( function(d) {
281
+ return d.properties.mag <= cfg.stars.designationLimit*adapt && clip(d.geometry.coordinates) === 1;
282
+ }))
283
+ .enter().append("text")
284
+ .attr("transform", function(d) { return point(d.geometry.coordinates); })
285
+ .text( function(d) { return starDesignation(d.id); })
286
+ .call(function (v) { attrs(v, {dy: ".85em", dx: ".35em", "class": "starDesignations"}); });
287
+ styles.starDesignations = svgTextStyle(cfg.stars.designationStyle);
288
+ }
289
+ if (cfg.stars.propername) {
290
+ groups.starNames.selectAll(".starnames")
291
+ .data(cons.features.filter( function(d) {
292
+ return d.properties.mag <= cfg.stars.propernameLimit*adapt && clip(d.geometry.coordinates) === 1;
293
+ }))
294
+ .enter().append("text")
295
+ .attr("transform", function(d) { return point(d.geometry.coordinates); })
296
+ .text( function(d) { return starPropername(d.id); })
297
+ .call(function (v) { attrs(v, {dy: "-.5em", dx: "-.35em", "class": "starNames"}); });
298
+
299
+ styles.starNames = svgTextStyle(cfg.stars.propernameStyle);
300
+ }
301
+ callback(null);
302
+ });
303
+ });
304
+ }
305
+
306
+ //Deep space objects
307
+ if (cfg.dsos.show) {
308
+ q.defer(function(callback) {
309
+ loadJson(path + cfg.dsos.data, function(error, json) {
310
+ if (error) callback(error);
311
+
312
+ var cond = getData(json, cfg.transform);
313
+
314
+ groups.dsos.selectAll(".dsos")
315
+ .data(cond.features.filter( function(d) {
316
+ return clip(d.geometry.coordinates) === 1 &&
317
+ (d.properties.mag === 999 && Math.sqrt(parseInt(d.properties.dim)) > cfg.dsos.limit*adapt ||
318
+ d.properties.mag !== 999 && d.properties.mag <= cfg.dsos.limit);
319
+ }))
320
+ .enter().append("path")
321
+ .attr("class", function(d) { return "dsos" + d.properties.type; })
322
+ .attr("transform", function(d) { return point(d.geometry.coordinates); })
323
+ .attr("d", function(d) { return dsoSymbol(d.properties); });
324
+
325
+ styles.dsos = svgStyle(cfg.dsos.style);
326
+
327
+ for (key in cfg.dsos.symbols) {
328
+ if (!has(cfg.dsos.symbols, key)) continue;
329
+ id = "dsos" + key;
330
+ styles[id] = { "fill-opacity": cfg.dsos.style.opacity, "stroke-opacity": cfg.dsos.style.opacity };
331
+ if (has(cfg.dsos.symbols[key], "stroke")) {
332
+ styles[id].fill = "none";
333
+ styles[id].stroke = cfg.dsos.colors ? cfg.dsos.symbols[key].stroke : cfg.dsos.style.stroke;
334
+ styles[id]["stroke-width"] = cfg.dsos.colors ? cfg.dsos.symbols[key].width : cfg.dsos.style.width;
335
+ } else {
336
+ styles[id].stroke = "none";
337
+ styles[id].fill = cfg.dsos.colors ? cfg.dsos.symbols[key].fill : cfg.dsos.style.fill;
338
+ }
339
+ }
340
+
341
+
342
+ if (cfg.dsos.names) {
343
+ groups.dsoNames.selectAll(".dsonames")
344
+ .data(cond.features.filter( function(d) {
345
+ return clip(d.geometry.coordinates) === 1 &&
346
+ (d.properties.mag == 999 && Math.sqrt(parseInt(d.properties.dim)) > cfg.dsos.nameLimit ||
347
+ d.properties.mag != 999 && d.properties.mag <= cfg.dsos.nameLimit);
348
+ }))
349
+ .enter().append("text")
350
+ .attr("class", function(d) { return "dsoNames " + d.properties.type; })
351
+ .attr("transform", function(d) { return point(d.geometry.coordinates); })
352
+ .text( function(d) { return dsoName(d); })
353
+ .call(function (v) { attrs(v, {dy: "-.5em", dx: ".35em"}); });
354
+
355
+ styles.dsoNames = {"fill-opacity": cfg.dsos.style.opacity,
356
+ "font": cfg.dsos.nameStyle.font,
357
+ "text-anchor": svgAlign(cfg.dsos.nameStyle.align)};
358
+
359
+ for (key in cfg.dsos.symbols) {
360
+ if (!has(cfg.dsos.symbols, key)) continue;
361
+ styles[key] = {"fill": cfg.dsos.colors ? cfg.dsos.symbols[key].fill : cfg.dsos.style.fill };
362
+ }
363
+ }
364
+ callback(null);
365
+ });
366
+ });
367
+ }
368
+
369
+ //Planets
370
+ if ((cfg.location || cfg.formFields.location) && cfg.planets.show && Celestial.origin) {
371
+ q.defer(function(callback) {
372
+ var dt = Celestial.date(),
373
+ o = Celestial.origin(dt).spherical(),
374
+ jp = {type: "FeatureCollection", features: []},
375
+ jlun = {type: "FeatureCollection", features: []};
376
+ sky.container.selectAll(".planet").each(function(d) {
377
+ var id = d.id(), r = 12,
378
+ p = d(dt).equatorial(o);
379
+
380
+ p.ephemeris.pos = transformDeg(p.ephemeris.pos, euler[cfg.transform]); //transform;
381
+ if (clip(p.ephemeris.pos) === 1) {
382
+ if (id === "lun")
383
+ jlun.features.push(createEntry(p));
384
+ else
385
+ jp.features.push(createEntry(p));
386
+ }
387
+ });
388
+ if (cfg.planets.symbolType === "disk") {
389
+ groups.planets.selectAll(".planets")
390
+ .data(jp.features)
391
+ .enter().append("path")
392
+ .attr("transform", function(d) { return point(d.geometry.coordinates); })
393
+ .attr("d", function(d) {
394
+ var r = (has(cfg.planets.symbols[d.id], "size")) ? (cfg.planets.symbols[d.id].size - 1) * adapt : null;
395
+ return planetSymbol(d.properties, r);
396
+ })
397
+ .attr("class", function(d) { return "planets " + d.id; });
398
+ } else {
399
+ groups.planets.selectAll(".planets")
400
+ .data(jp.features)
401
+ .enter().append("text")
402
+ .attr("transform", function(d) { return point(d.geometry.coordinates); })
403
+ .text( function(d) { return d.properties.symbol; })
404
+ .attr("class", function(d) { return "planets " + d.id; })
405
+ .call(function (v) { attrs(v, {dy: ".35em"}); });
406
+ }
407
+ // Special case for Moon crescent
408
+ if (jlun.features.length > 0) {
409
+ if (cfg.planets.symbolType === "letter") {
410
+ groups.planets.selectAll(".moon")
411
+ .data(jlun.features)
412
+ .enter().append("text")
413
+ .attr("transform", function(d) { return point(d.geometry.coordinates); })
414
+ .text( function(d) { return d.properties.symbol; })
415
+ .attr("class", function(d) { return "planets " + d.id; })
416
+ .call(function (v) { attrs(v, {dy: ".35em"}); });
417
+ } else {
418
+ var rl = has(cfg.planets.symbols.lun, "size") ? (cfg.planets.symbols.lun.size - 1) * adapt : 11 * adapt;
419
+ groups.planets.selectAll(".dmoon")
420
+ .data(jlun.features)
421
+ .enter().append("path")
422
+ .attr("class", "darkluna" )
423
+ .attr("transform", function(d) { return point(d.geometry.coordinates); })
424
+ .attr("d", function(d) { return d3.symbol().type(d3.symbolCircle).size(rl*rl)(); });
425
+ groups.planets.selectAll(".moon")
426
+ .data(jlun.features)
427
+ .enter().append("path")
428
+ .attr("class", function(d) { return "planets " + d.id; })
429
+ .attr("transform", function(d) { return point(d.geometry.coordinates); })
430
+ .attr("d", function(d) { return moonSymbol(d.properties, rl); });
431
+ }
432
+ }
433
+
434
+ styles.planets = svgTextStyle(cfg.planets.symbolStyle);
435
+ styles.planets.font = planetFont(cfg.planets.symbolStyle.font);
436
+ styles.darkluna = {"fill": "#557"};
437
+ for (key in cfg.planets.symbols) {
438
+ if (!has(cfg.planets.symbols, key)) continue;
439
+ styles[key] = {"fill": cfg.planets.symbols[key].fill};
440
+ }
441
+
442
+ //Planet names
443
+ if (cfg.planets.names) {
444
+ groups.planetNames.selectAll(".planetnames")
445
+ .data(jp.features)
446
+ .enter().append("text")
447
+ .attr("transform", function(d) { return point(d.geometry.coordinates); })
448
+ .text( function(d) { return d.properties.name; })
449
+ .call(function (v) { attrs(v, {dy: ".85em", dx: "-.35em"}); })
450
+ .attr("class", function(d) { return "planetNames " + d.id; });
451
+ if (jlun.features.length > 0) {
452
+ groups.planetNames.selectAll(".moonname")
453
+ .data(jlun.features)
454
+ .enter().append("text")
455
+ .attr("transform", function(d) { return point(d.geometry.coordinates); })
456
+ .text( function(d) { return d.properties.name; })
457
+ .call(function (v) { attrs(v, {dy: ".85em", dx: "-.35em"}); })
458
+ .attr("class", function(d) { return "planetNames " + d.id; });
459
+ }
460
+ }
461
+ styles.planetNames = svgTextStyle(cfg.planets.nameStyle);
462
+
463
+ callback(null);
464
+ });
465
+ }
466
+
467
+ if ((cfg.location || cfg.formFields.location) && cfg.daylight.show && proj.clip) {
468
+ q.defer(function(callback) {
469
+ var sol = getPlanet("sol", undefined, sky);
470
+ if (sol) {
471
+ var up = Celestial.zenith(),
472
+ solpos = sol.ephemeris.pos,
473
+ dist = d3.geoDistance(up, solpos),
474
+ pt = projection(solpos),
475
+ daylight = d3.geoCircle().radius(179.95).center(solpos);
476
+
477
+ groups.daylight.append("path").datum(daylight)
478
+ .attr("class", "daylight")
479
+ .attr("d", map);
480
+
481
+ styles.daylight = svgSkyStyle(dist, pt);
482
+
483
+ if (clip(solpos) === 1 && dist < halfπ) {
484
+ groups.daylight.append("circle")
485
+ .attr("cx", pt[0])
486
+ .attr("cy", pt[1])
487
+ .attr("r", 5)
488
+ .style("fill", "#fff");
489
+ }
490
+ }
491
+ callback(null);
492
+ });
493
+ }
494
+
495
+ if ((cfg.location || cfg.formFields.location) && cfg.horizon.show && !proj.clip) {
496
+ q.defer(function(callback) {
497
+ var horizon = d3.geoCircle().radius(90).center(Celestial.nadir());
498
+
499
+ groups.horizon.append("path").datum(horizon)
500
+ .attr("class", "horizon")
501
+ .attr("d", map);
502
+ styles.horizon = svgStyle(cfg.horizon);
503
+ callback(null);
504
+ });
505
+ }
506
+
507
+ if (Celestial.data.length > 0) {
508
+ Celestial.data.forEach( function(d) {
509
+ if (has(d, "save")) {
510
+ q.defer(function(callback) {
511
+ d.save();
512
+ callback(null);
513
+ });
514
+ }
515
+ });
516
+ }
517
+
518
+ // Helper functions
519
+
520
+ function clip(coords) {
521
+ return proj.clip && d3.geoDistance(center, coords) > halfπ ? 0 : 1;
522
+ }
523
+
524
+ function point(coords) {
525
+ return "translate(" + projection(coords) + ")";
526
+ }
527
+
528
+ function filename(what, sub, ext) {
529
+ var cult = (has(formats[what], culture)) ? "." + culture : "";
530
+ ext = ext ? "." + ext : ".json";
531
+ sub = sub ? "." + sub : "";
532
+ return what + sub + cult + ext;
533
+ }
534
+
535
+ function svgStyle(s) {
536
+ var res = {};
537
+ res.fill = s.fill || "none";
538
+ res["fill-opacity"] = s.opacity !== null ? s.opacity : 1;
539
+ res.stroke = s.stroke || "none";
540
+ res["stroke-width"] = s.width !== null ? s.width : 0;
541
+ res["stroke-opacity"] = s.opacity !== null ? s.opacity : 1;
542
+ if (has(s, "dash")) res["stroke-dasharray"] = s.dash.join(" ");
543
+ else res["stroke-dasharray"] = "none";
544
+ res.font = s.font || null;
545
+ return res;
546
+ }
547
+
548
+ function svgTextStyle(s) {
549
+ var res = {};
550
+ res.stroke = "none";
551
+ res.fill = s.fill || "none";
552
+ res["fill-opacity"] = s.opacity !== null ? s.opacity : 1;
553
+ //res.textBaseline = s.baseline || "bottom";
554
+ res["text-anchor"] = svgAlign(s.align);
555
+ res.font = s.font || null;
556
+ return res;
557
+ }
558
+
559
+ function svgStyleA(rank, s) {
560
+ var res = {};
561
+ rank = rank || 1;
562
+ res.fill = isArray(s.fill) ? s.fill[rank-1] : null;
563
+ res["fill-opacity"] = isArray(s.opacity) ? s.opacity[rank-1] : 1;
564
+ res.stroke = isArray(s.stroke) ? s.stroke[rank-1] : null;
565
+ res["stroke-width"] = isArray(s.width) ? s.width[rank-1] : null;
566
+ res["stroke-opacity"] = isArray(s.opacity) ? s.opacity[rank-1] : 1;
567
+ res["text-anchor"] = svgAlign(s.align);
568
+ res.font = isArray(s.font) ? s.font[rank-1] : null;
569
+ //res.textBaseline = s.baseline || "bottom";
570
+ return res;
571
+ }
572
+
573
+ function svgSkyStyle(dist, pt) {
574
+ var factor, color1, color2, color3,
575
+ upper = 1.36,
576
+ lower = 1.885;
577
+
578
+ if (dist > lower) return {fill: "transparent"};
579
+
580
+ if (dist <= upper) {
581
+ color1 = "#daf1fa";
582
+ color2 = "#93d7f0";
583
+ color3 = "#57c0e8";
584
+ factor = -(upper-dist) / 10;
585
+ } else {
586
+ factor = (dist - upper) / (lower - upper);
587
+ color1 = d3.interpolateLab("#daf1fa", "#e8c866")(factor);
588
+ color2 = d3.interpolateLab("#93c7d0", "#ff854a")(factor);
589
+ color3 = d3.interpolateLab("#57b0c8", "#6caae2")(factor);
590
+ }
591
+
592
+ var gradient = groups.daylight.append("radialGradient")
593
+ .attr("cx", pt[0])
594
+ .attr("cy", pt[1])
595
+ .attr("fr", "0")
596
+ .attr("r", "300")
597
+ .attr("id", "skygradient")
598
+ .attr("gradientUnits", "userSpaceOnUse");
599
+
600
+ gradient.append("stop").attr("offset", "0").attr("stop-color", color1);
601
+ gradient.append("stop").attr("offset", 0.2+0.4*factor).attr("stop-color", color2);
602
+ gradient.append("stop").attr("offset", "1").attr("stop-color", color3);
603
+
604
+ return {"fill": "url(#skygradient)", "fill-opacity": skyTransparency(factor, 1.4)};
605
+ }
606
+
607
+ function skyTransparency(t, a) {
608
+ return 0.9 * (1 - ((Math.pow(Math.E, t*a) - 1) / (Math.pow(Math.E, a) - 1)));
609
+ }
610
+
611
+
612
+
613
+ function svgAlign(s) {
614
+ if (!s) return "start";
615
+ if (s === "center") return "middle";
616
+ if (s === "right") return "end";
617
+ return "start";
618
+ }
619
+
620
+ function dsoSymbol(p) {
621
+ var size = dsoSize(p.mag, p.dim) || 9,
622
+ type = dsoShape(p.type);
623
+ var beepitett = builtinSymbol(type);
624
+ if (beepitett) {
625
+ return d3.symbol().type(beepitett).size(size)();
626
+ } else {
627
+ return customSymbol().type(type).size(size)();
628
+ }
629
+ }
630
+
631
+ function dsoShape(type) {
632
+ if (!type || !has(cfg.dsos.symbols, type)) return "circle";
633
+ else return cfg.dsos.symbols[type].shape;
634
+ }
635
+
636
+ function dsoSize(mag, dim) {
637
+ if (!mag || mag === 999) return Math.pow(parseInt(dim) * cfg.dsos.size * adapt / 7, 0.5);
638
+ return Math.pow(2 * cfg.dsos.size * adapt - mag, cfg.dsos.exponent);
639
+ }
640
+
641
+ function dsoName(d) {
642
+ //return p[cfg.dsos.namesType];
643
+ var lang = cfg.dsos.namesType, id = d.id;
644
+ if (lang === "desig" || !has(sky.dsonames, id)) return d.properties.desig;
645
+ return has(sky.dsonames[id], lang) ? sky.dsonames[id][lang] : d.properties.desig;
646
+ }
647
+
648
+ function dsoColor(p) {
649
+ if (cfg.dsos.colors === true) return svgStyle(cfg.dsos.symbols[p.type]);
650
+ return svgStyle(cfg.dsos.style);
651
+ }
652
+
653
+ function starDesignation(id) {
654
+ if (!has(sky.starnames, id)) return "";
655
+ return sky.starnames[id][cfg.stars.designationType];
656
+ }
657
+
658
+ function starPropername(id) {
659
+ var lang = cfg.stars.propernameType;
660
+ if (!has(sky.starnames, id)) return "";
661
+ return has(sky.starnames[id], lang) ? sky.starnames[id][lang] : sky.starnames[id].name;
662
+ }
663
+
664
+ function starSize(mag) {
665
+ if (mag === null) return 0.1;
666
+ var d = cfg.stars.size * adapt * Math.exp(cfg.stars.exponent * (mag + 2));
667
+ return Math.max(d, 0.1);
668
+ }
669
+
670
+ function starColor(bv) {
671
+ if (!cfg.stars.colors || isNaN(bv)) return "";
672
+ return Math.round(bv*10).toString();
673
+ }
674
+
675
+ function constName(d) {
676
+ return d.properties[cfg.constellations.namesType];
677
+ }
678
+
679
+ function moonSymbol(p, r) {
680
+ var size = r ? r*r : 121;
681
+ return customSymbol().type("crescent").size(size).ratio(p.age)();
682
+ }
683
+
684
+ function planetSymbol(p, r) {
685
+ var size = r ? r*r : planetSize(p.mag);
686
+ return d3.symbol().type(d3.symbolCircle).size(size)();
687
+ }
688
+
689
+ function planetFont(s) {
690
+ var size = s.replace(/(^\D*)(\d+)(\D.+$)/i,'$2');
691
+ size = Math.round(adapt * size);
692
+ return s.replace(/(^\D*)(\d+)(\D.+$)/i,'$1' + size + '$3');
693
+ }
694
+
695
+ function planetSize(m) {
696
+ var mag = m || 2;
697
+ var r = 4 * adapt * Math.exp(-0.05 * (mag+2));
698
+ return Math.max(r, 2);
699
+ }
700
+
701
+ function createEntry(o) {
702
+ var res = {type: "Feature", "id":o.id, properties: {}, geometry:{}};
703
+ res.properties.name = o[cfg.planets.namesType];
704
+ if (cfg.planets.symbolType === "symbol" || cfg.planets.symbolType === "letter")
705
+ res.properties.symbol = cfg.planets.symbols[res.id][cfg.planets.symbolType];
706
+ res.properties.mag = o.ephemeris.mag || 10;
707
+ if (res.id === "lun") {
708
+ res.properties.age = o.ephemeris.age;
709
+ res.properties.phase = o.ephemeris.phase;
710
+ }
711
+ res.geometry.type = "Point";
712
+ res.geometry.coordinates = o.ephemeris.pos;
713
+ return res;
714
+ }
715
+
716
+ function createStyles() {
717
+ var res = "";
718
+ for (var key in styles) {
719
+ if (!has(styles, key)) continue;
720
+ res += " ." + key + stringifyStyle(styles[key]);
721
+ }
722
+ return "/*\u003c![CDATA[*/\n" + res + "\n/*]]\u003e*/";
723
+ }
724
+
725
+ function stringifyStyle(s) {
726
+ var res = " {";
727
+ for (var key in s) {
728
+ if (!has(s, key)) continue;
729
+ res += key + ":" + s[key] + "; ";
730
+ }
731
+ return res + "} ";
732
+ }
733
+
734
+ q.await(function(error) {
735
+ if (error) throw error;
736
+ var svg = d3.select("#d3-celestial-svg svg")
737
+ .attr("title", "D3-Celestial")
738
+ .attr("version", 1.1)
739
+ .attr("encoding", "UTF-8")
740
+ .attr("xmlns", "http://www.w3.org/2000/svg")
741
+ .attr("xmlns:xlink", "http://www.w3.org/1999/xlink")
742
+ .attr("xmlns:sodipodi", "http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd")
743
+ .attr("xmlns:inkscape", "http://www.inkscape.org/namespaces/inkscape")
744
+ .attr("viewBox", " 0 0 " + (m.width) + " " + (m.height));
745
+
746
+ defs.append("style")
747
+ .attr("type", "text\/css")
748
+ .text(createStyles());
749
+ /*defs.append(":sodipodi:namedview")
750
+ .attr(":inkscape:window-width", m.width+200)
751
+ .attr(":inkscape:window-height", m.height)
752
+ .attr(":inkscape:window-maximized", "1");*/
753
+ if (fname) {
754
+ var blob = new Blob([svg.node().outerHTML], {type:"image/svg+xml;charset=utf-8"});
755
+
756
+ var a = d3.select("body").append("a").node();
757
+ a.download = fname || "d3-celestial.svg";
758
+ a.rel = "noopener";
759
+ a.href = URL.createObjectURL(blob);
760
+ a.click();
761
+ d3.select(a).remove();
762
+ } else if (done !== null) {
763
+ done(svg.node().outerHTML);
764
+ }
765
+ d3.select("#d3-celestial-svg").remove();
766
+ });
767
+
768
+ }
769
+
770
+ var customSvgSymbols = new Map(Object.entries({
771
+ 'ellipse': function(size, ratio) {
772
+ var s = Math.sqrt(size),
773
+ rx = s*0.666, ry = s/3;
774
+ return 'M' + (-rx) + ',' + (-ry) +
775
+ ' m' + (-rx) + ',0' +
776
+ ' a' + rx + ',' + ry + ' 0 1,0' + (rx * 2) + ',0' +
777
+ ' a' + rx + ',' + ry + ' 0 1,0' + (-(rx * 2)) + ',0';
778
+ },
779
+ 'marker': function(size, ratio) {
780
+ var s = size > 48 ? size / 4 : 12,
781
+ r = s/2, l = r-3;
782
+ return 'M ' + (-r) + ' 0 h ' + l +
783
+ ' M 0 ' + (-r) + ' v ' + l +
784
+ ' M ' + r + ' 0 h ' + (-l) +
785
+ ' M 0 ' + r + ' v ' + (-l);
786
+ },
787
+ 'cross-circle': function(size, ratio) {
788
+ var s = Math.sqrt(size),
789
+ r = s/2;
790
+ return 'M' + (-r) + ',' + (-r) +
791
+ ' m' + (-r) + ',0' +
792
+ ' a' + r + ',' + r + ' 0 1,0' + (r * 2) + ',0' +
793
+ ' a' + r + ',' + r + ' 0 1,0' + (-(r * 2)) + ',0' +
794
+ ' M' + (-r) + ' 0 h ' + (s) +
795
+ ' M 0 ' + (-r) + ' v ' + (s);
796
+
797
+ },
798
+ 'stroke-circle': function(size, ratio) {
799
+ var s = Math.sqrt(size),
800
+ r = s/2;
801
+ return 'M' + (-r) + ',' + (-r) +
802
+ ' m' + (-r) + ',0' +
803
+ ' a' + r + ',' + r + ' 0 1,0' + (r * 2) + ',0' +
804
+ ' a' + r + ',' + r + ' 0 1,0' + (-(r * 2)) + ',0' +
805
+ ' M' + (-s-2) + ',' + (-s-2) + ' l' + (s+4) + ',' + (s+4);
806
+
807
+ },
808
+ "crescent": function(size, ratio) {
809
+ var s = Math.sqrt(size),
810
+ r = s/2,
811
+ ph = 0.5 * (1 - Math.cos(ratio)),
812
+ // The terminator's semi-minor axis is |cos(phase angle)| times the disc
813
+ // radius, which in terms of the illuminated fraction is
814
+ // |2*ph - 1| = 2*|ph - 0.5|. The factor of 1.6 drew a full moon 19% too
815
+ // narrow — as a gibbous disc (#130). 1.98 + 0.01 gives exactly 1 at full
816
+ // and new moon while keeping the guard against a degenerate ellipse at
817
+ // the quarters.
818
+ e = 1.98 * Math.abs(ph - 0.5) + 0.01,
819
+ dir = ratio > Math.PI ? 0 : 1,
820
+ termdir = Math.abs(ph) > 0.5 ? dir : Math.abs(dir-1);
821
+ return 'M ' + (-1) + ',' + (-1) +
822
+ ' m 1,' + (-r+1) +
823
+ ' a' + r + ',' + r + ' 0 1 ' + dir + ' 0,' + (r * 2) +
824
+ ' a' + (r*e) + ',' + r + ' 0 1 ' + termdir + ' 0,' + (-(r * 2)) + 'z';
825
+ }
826
+ }));
827
+
828
+ // In v3 a symbol type was a string ("circle"); in v7 it is an object.
829
+ // Resolved in a function rather than a module-level table, so that svg.js does
830
+ // not reference d3 at load time and stays testable from Node.
831
+ function builtinSymbol(name_) {
832
+ switch (name_) {
833
+ case "circle": return d3.symbolCircle;
834
+ case "cross": return d3.symbolCross;
835
+ case "diamond": return d3.symbolDiamond;
836
+ case "square": return d3.symbolSquare;
837
+ case "star": return d3.symbolStar;
838
+ case "wye": return d3.symbolWye;
839
+ // v3 knew a separate downward triangle; v7 does not.
840
+ case "triangle": case "triangle-up": case "triangle-down": return d3.symbolTriangle;
841
+ default: return null;
842
+ }
843
+ }
844
+
845
+ function customSymbol() {
846
+ var type, size = 64, ratio = functor(1);
847
+
848
+ function symbol(d,i) {
849
+ return customSvgSymbols.get(type.call(this,d,i))(size.call(this,d,i), ratio.call(this,d,i));
850
+ }
851
+ symbol.type = function(_) {
852
+ if (!arguments.length) return type;
853
+ type = functor(_);
854
+ return symbol;
855
+ };
856
+ symbol.size = function(_) {
857
+ if (!arguments.length) return size;
858
+ size = functor(_);
859
+ return symbol;
860
+ };
861
+ symbol.ratio = function(_) {
862
+ if (!arguments.length) return ratio;
863
+ ratio = functor(_);
864
+ return symbol;
865
+ };
866
+ return symbol;
867
+ }
868
+
869
+
870
+
871
+ // Backwards compatible: the global interface acts on the most recent map.
872
+ // Per instance, the object returned by Celestial.display() carries its own
873
+ // exportSVG, which exports that map.
874
+ Celestial.exportSVG = function(callback) {
875
+ if (!callback) return;
876
+ exportSVG(this && this.cfg ? this : Celestial, callback);
877
+ };
878
+
879
+ export { customSvgSymbols, exportSVG };