d3-celestial

repository·master·Indexed 20 days ago

https://github.com/ofrohn/d3-celestial

A D3.js-based library for creating interactive, canvas-rendered celestial maps. It enables the visualization of stars, constellations, deep sky objects (DSOs), and planets using GeoJSON data. The library supports multiple coordinate systems (ecliptic, galactic, supergalactic), zoom and rotation interactivity, and customizable rendering for stellar magnitudes and astronomical symbols. Version 0.7.35.

Tokens
16.5K
Snippets
45
Records
56
Agent score
71%

What's inside d3-celestial

  1. Overview of d3-celestial

    master

    d3-celestial is an interactive, adaptable celestial map built using the D3.js visualization library and HTML5 Canvas. It uses GeoJSON for celestial data and supports the display of stars, deep sky objects (DSOs), constellations (names, lines, and boundaries), the Milky Way band, and grid lines.

    Key features include:

    • Selectable Magnitude: Display stars up to magnitude 6, or use custom GeoJSON sources for higher magnitudes.
    • Coordinate Systems: Supports multiple coordinate spaces such as ecliptic, galactic, or supergalactic.
    • Interactivity: Full support for zoom and rotation via mouse or gestures.
    • Browser Support: Requires a modern browser with Canvas support (Chrome, Firefox, Safari, Opera, or IE 9+).
  2. Understand the data format and coordinate system

    master

    The celestial data used by d3-celestial is converted to GeoJSON at the J2000 epoch.

    To comply with GeoJSON requirements, star positions are converted from Right Ascension (0...24h) to Longitude (-180...180 degrees) as follows:

    • 0...12h Right Ascension maps to 0...180 degrees Longitude.
    • 12...24h Right Ascension maps to -180...0 degrees Longitude.
  3. Avoid overlapping labels using a Quadtree

    master

    To prevent labels from overlapping in crowded areas, you can use d3.geom.quadtree to manage label proximity in pixel-space.

    Implementation Pattern:

    1. Initialize Quadtree: Create a quadtree with an extent matching the map dimensions (obtained via Celestial.metrics()).
    2. Define Proximity Limit: Set a minimum pixel distance (PROXIMITY_LIMIT) between labels.
    3. Check Proximity during Redraw: For each label, use quadtree.find(pt) to find the nearest existing label.
    4. Conditional Drawing: Only draw the label and add its position to the quadtree if the distance to the nearest neighbor exceeds your limit.

    This approach scales with zoom levels because the proximity check is performed in pixel-space.

    var PROXIMITY_LIMIT = 20;
    var m = Celestial.metrics(); 
    var quadtree = d3.geom.quadtree().extent([[-1, -1], [m.width + 1, m.height + 1]])([]);
    
    // Inside redraw loop:
    var nearest = quadtree.find(pt);
    if (!nearest || distance(nearest, pt) > PROXIMITY_LIMIT) { 
      quadtree.add(pt);
      // Draw the label here...
    }
    
    function distance(p1, p2) {
      var d1 = p2[0] - p1[0],
          d2 = p2[1] - p1[1];
      return Math.sqrt(d1 * d1 + d2 * d2);
    }
  4. Add custom GeoJSON data to the celestial map

    master

    To add custom astronomical data (like asterisms or constellations), you must provide valid GeoJSON. Coordinates should be in degrees: Right Ascension (RA) from -180 to 180, and Declination (Dec) from -90 to 90.

    If your data uses RA in hours (0-24h), convert it to degrees using the formula: ra > 12 ? (ra - 24) * 15 : ra * 15.

    Use Celestial.add() to inject data. You can provide a file (URL to JSON) or raw data. The callback is used to load/transform the data into the D3 container, and the redraw function is called whenever the map updates to render the new elements using the Celestial.context (canvas) or Celestial.container (SVG/D3).

    Note: Since the map uses a canvas for performance, you typically use Celestial.map(d) to project the data and then use standard Canvas API methods like fill() or stroke() within the redraw callback.

    var jsonLine = {
      "type":"FeatureCollection",
      "features":[
        {
          "type":"Feature",
          "id":"SummerTriangle",
          "properties": {
            "n":"Summer Triangle",
            "loc": [-67.5, 52]
          },
          "geometry":{
            "type":"MultiLineString",
            "coordinates":[[[-80.7653, 38.7837], [-62.3042, 8.8683], [-49.642, 45.2803], [-80.7653, 38.7837]]]
          }
        }
      ]
    };
    
    Celestial.add({
      type: 'raw',
      callback: function(error, json) {
        if (error) return console.warn(error);
        var asterism = Celestial.getData(jsonLine, config.transform);
        Celestial.container.selectAll(".asterisms")
          .data(asterism.features)
          .enter().append("path")
          .attr("class", "ast"); 
        Celestial.redraw();
      },
      redraw: function() {   
        Celestial.container.selectAll(".ast").each(function(d) {   
          Celestial.setStyle(lineStyle);
          Celestial.map(d);
          Celestial.context.fill();
          Celestial.context.stroke();
        });
      }
    });
  5. Manually draw points on the canvas

    master

    Because points do not automatically render through the standard Celestial.map projection, you must implement a manual drawing loop within the redraw function of Celestial.add().

    Steps for manual point rendering:

    1. Select objects: Use Celestial.container.selectAll(".your-class-name") to iterate over your data.
    2. Visibility Check: Use Celestial.clip(coordinates) to check if the point is currently within the visible map area.
    3. Projection: Use Celestial.mapProjection(coordinates) to convert celestial coordinates to pixel coordinates [x, y].
    4. Styling: Use Celestial.setStyle(pointStyle) for object shapes and Celestial.setTextStyle(textStyle) for labels.
    5. Canvas Drawing: Use standard HTML5 Canvas API commands (e.g., Celestial.context.arc(), Celestial.context.fill(), Celestial.context.stroke()) to draw the shape and Celestial.context.fillText() for labels.
    // Inside the redraw function of Celestial.add()
    Celestial.container.selectAll(".snr").each(function(d) {
      // 1. Check visibility
      if (Celestial.clip(d.geometry.coordinates)) {
        // 2. Project coordinates
        var pt = Celestial.mapProjection(d.geometry.coordinates);
        var r = 5; // example radius
    
        // 3. Draw shape
        Celestial.setStyle(pointStyle);
        Celestial.context.beginPath();
        Celestial.context.arc(pt[0], pt[1], r, 0, 2 * Math.PI);
        Celestial.context.closePath();
        Celestial.context.stroke();
        Celestial.context.fill();
    
        // 4. Draw label
        Celestial.setTextStyle(textStyle);
        Celestial.context.fillText(d.properties.name, pt[0] + r, pt[1] - r);
      }
    });
  6. Set up d3-celestial in your HTML

    master

    To use d3-celestial, follow these steps to prepare your HTML structure and include the necessary dependencies:

    1. Create a container div: Add a <div> with a specific ID where the map will be rendered (e.g., <div id="celestial-map"></div>).
    2. Optional Form div: If you intend to use the built-in configuration forms, add a <div> with the ID celestial-form.
    3. Include D3.js dependencies: You must include d3.min.js and d3.geo.projection.min.js.
    4. Include d3-celestial: Include the celestial.js or celestial.min.js script.

    Local Development Note: If running locally without a web server, Chrome requires the --allow-file-access-from-files command-line parameter to allow loading local JSON data files. Alternatively, use a local web server (e.g., via Node.js).

    <!-- 1. Map container -->
    <div id="celestial-map"></div>
    
    <!-- 2. Optional form container -->
    <div id="celestial-form"></div>
    
    <!-- 3. D3 dependencies -->
    <script src="http://d3js.org/d3.min.js"></script>
    <script src="http://d3js.org/d3.geo.projection.min.js"></script>
    
    <!-- 4. d3-celestial -->
    <script src="celestial.min.js"></script>
  7. Add point sources to the celestial map

    master

    To add custom point sources (like supernovae or stars) to the map, you must provide data in a GeoJSON FeatureCollection format. Each feature should be a Point with coordinates representing [ra, dec] in degrees (range: [-180..180, -90..90]).

    To control appearance, define style objects using CSS-like formats for pointStyle (stroke, width, fill) and textStyle (fill, font, align, baseline).

    Use Celestial.add() to register the data. This function requires a callback to load/transform the data into the Celestial container and a redraw function to handle the manual canvas drawing of points, as points do not automatically render via the standard map projection pipeline.

    var jsonSN = {
      "type":"FeatureCollection",
      "features":[
        {
          "type":"Feature",
          "id":"SomeDesignator",
          "properties": {
            "name":"Some Name",
            "mag": 10,
            "dim": 30
          }, 
          "geometry":{
            "type":"Point",
            "coordinates": [-80.7653, 38.7837]
          }
        }  
      ]
    };
    
    var pointStyle = { 
      stroke: "#f0f", 
      width: 3,
      fill: "rgba(255, 204, 255, 0.4)"
    };
    
    var textStyle = {
      fill:"#f0f", 
      font: "bold 15px Helvetica, Arial, sans-serif", 
      align: "left", 
      baseline: "bottom" 
    };
  8. How the Keplerian orbital model works

    master

    The library includes a Kepler class to calculate the positions of celestial bodies using Keplerian elements.

    To calculate a body's position, you provide its orbital elements:

    • a: Semi-major axis
    • e: Eccentricity
    • i: Inclination
    • N: Longitude of the ascending node
    • w or W: Argument of periapsis or longitude of periapsis
    • M or L: Mean anomaly or mean longitude
    • dM or n: Rate of change of mean anomaly or mean daily motion
    • ep: Epoch

    Elements can be updated via the .elements(obj) method, and the resulting position can be retrieved in Cartesian, Spherical, or Equatorial coordinates.

    // Conceptual usage of the Kepler engine
    const body = new Kepler()
      .elements({ a: 1.0, e: 0.0167, i: 0, N: 0, w: 0, M: 0, n: 0.9856 })
      .kepler(new Date())
      .cartesian(); // Returns {x, y, z, ...}
  9. How map animations and transitions work

    master

    Celestial uses D3.js transitions to interpolate between different map states.

    • Rotation/Center Transitions: Uses d3.geo.interpolate to smoothly transition the center coordinates. The duration is automatically scaled based on the angular distance to ensure a consistent feel.
    • Zoom Transitions: Interpolates the scale factor using d3.interpolateNumber. It respects the zoomextent defined in the configuration.
    • Projection Transitions: Uses a custom tweening mechanism to interpolate the projection's ratio and scale, allowing for smooth transitions between different map views (e.g., from equatorial to stereographic).
  10. Configure planet display settings

    master

    The planets object controls the visibility and appearance of planets. Note that planets.show must be true and a date-time must be set for planets to appear.

    Planet Configuration Options

    • show: Boolean to enable/disable planets.
    • which: Array of object identifiers to show (e.g., ["sol", "mer", "ven", "ter", "lun", "mar", "jup", "sat", "ura", "nep"]).
    • symbols: An object mapping identifiers to specific symbols (Unicode characters), letters, and colors.
    • symbolStyle: Object defining fill, font, align, and baseline for the symbols.
    • symbolType: The rendering mode: 'symbol' (graphic planet sign), 'disk' (filled circle scaled by magnitude), or 'letter' (1 or 2 letters).
    • names: Boolean; show name next to symbol.
    • nameStyle: Object defining fill, font, align, and baseline for names.
    • namesType: Language/type of planet name ('desig' or language code).

    Example Planet Symbol Mapping

    symbols: {
      "sol": {symbol: "\u2609", letter:"Su", fill: "#ffff00", size:""},
      "mer": {symbol: "\u263f", letter:"Me", fill: "#cccccc"},
      "ven": {symbol: "\u2640", letter:"V", fill: "#eeeecc"},
      "ter": {symbol: "\u2295", letter:"T", fill: "#00ccff"},
      "lun": {symbol: "\u25cf", letter:"L", fill: "#ffffff", size:""},
      "mar": {symbol: "\u2642", letter:"Ma", fill: "#ff6600"},
      "jup": {symbol: "\u2643", letter:"J", fill: "#ffaa33"},
      "sat": {symbol: "\u2644", letter:"Sa", fill: "#ffdd66"},
      "ura": {symbol: "\u2645", letter:"U", fill: "#66ccff"},
      "nep": {symbol: "\u2646", letter:"N", fill: "#6666ff"}
    }
    planets: {
      show: false,
      which: ["sol", "mer", "ven", "ter", "lun", "mar", "jup", "sat", "ura", "nep"],
      symbols: {
        "sol": {symbol: "\u2609", letter:"Su", fill: "#ffff00", size:""},
        "mer": {symbol: "\u263f", letter:"Me", fill: "#cccccc"},
        "ven": {symbol: "\u2640", letter:"V", fill: "#eeeecc"},
        "ter": {symbol: "\u2295", letter:"T", fill: "#00ccff"},
        "lun": {symbol: "\u25cf", letter:"L", fill: "#ffffff", size:""},
        "mar": {symbol: "\u2642", letter:"Ma", fill: "#ff6600"},
        "cer": {symbol: "\u26b3", letter:"C", fill: "#cccccc"},
        "ves": {symbol: "\u26b6", letter:"Ma", fill: "#cccccc"},
        "jup": {symbol: "\u2643", letter:"J", fill: "#ffaa33"},
        "sat": {symbol: "\u2644", letter:"Sa", fill: "#ffdd66"},
        "ura": {symbol: "\u2645", letter:"U", fill: "#66ccff"},
        "nep": {symbol: "\u2646", letter:"N", fill: "#6666ff"},
        "plu": {symbol: "\u2647", letter:"P", fill: "#aaaaaa"},
        "eri": {symbol: "\u26aa", letter:"E", fill: "#eeeeee"}
      },
      symbolStyle: { fill: "#00ccff", font: "bold 17px 'Lucida Sans Unicode', Consolas, sans-serif", align: "center", baseline: "middle" },
      symbolType: "symbol",
      names: false,
      nameStyle: { fill: "#00ccff", font: "14px 'Lucida Sans Unicode', Consolas, sans-serif", align: "right", baseline: "top" },
      namesType: "desig"
    }
  11. Configure the d3-celestial map

    master

    The d3-celestial library is configured using a single JavaScript object passed to the Celestial.display(config) method. This object controls the map projection, coordinate transformations, visual styles for stars, deep space objects (DSOs), planets, constellations, and the Milky Way, as well as interactive settings.

    Core Map Settings

    • projection: The map projection to use (e.g., `
    var config = {
      width: 0,           // 0 = full parent element width
      projection: "aitoff",    // Map projection used
      projectionRatio: null,   // Optional override for default projection ratio
      transform: "equatorial", // equatorial (default), ecliptic, galactic, supergalactic
      center: null,       // [longitude, latitude, orientation] in degrees
      orientationfixed: true,  // Keep orientation angle the same as center[2]
      geopos: null,       // [lat,lon] in degrees, overrides center
      follow: "zenith",   // On which coordinates to center the map
      zoomlevel: null,    // initial zoom level 0...zoomextend
      zoomextend: 10,     // maximum zoom level
      adaptable: true,    // Sizes increase with higher zoom-levels
      interactive: true,  // Enable zooming and rotation
      form: true,         // Display interactive settings form
      container: "map",  // ID of parent element
      datapath: "data/",  // Path/URL to data files
      lang: "",           // Global language override
      culture: "",        // Source of names (default "iau", or "cn")
      daterange: [],       // Calendar date range
      controls: true       // Display zoom controls
    };
    
    Celestial.display(config);