d3_geo_rs 0.1.5

A port of D3/d3-geo
Documentation

Rust D3 Geo

Rust 2021 Edition.

This is a port d3-geo into RUST. Not all projection have been implemented and there are number of known bugs that I am activley working on.

Here is a list of the currently supported projections.

  • AziumuthalEqualArea
  • AzimuthalEquiDistant
  • Equirectangular
  • Gnomic
  • Mercator
  • Orthographic
  • Stereographic

Examples

Examples are provided to help developers convert their existing javascript to rust. To run the example please follow the "Running the examples" below.

As a confidence building exercise, this demo shows a side by side comparison of the all the projections rendered by in both javascript and rust.

When to use the rust version of the library

The limits of the javascript library become obvious when developing interactive applications that process large datasets. For example the examples/globe applications operate on a 50m resolution map of the earth. On a desktop machine this is beyond the javascript version.

Current Status

The majority of the library has been ported along with the associated tests. The aim is to eventaully release this as a rust crate. but at the moment is this alpha grade software.

  • The recenter state-based API refacor is almost complete. Once fit_size_resampling() is reinstated the code-coverage metric will jump 10% back to the previous value of approx 82%

  • The API is not stabalised. If perfomance issues arise then the API will change. Additionaly I plan a final review to remove anything uneeded before making changes become complicated.

  • The clipping algorithm in clip/rejoin/mod.rs needs to be refactored. see The intersection Problem. Test coverage in that area is high so the algortihms is working but the data structures make extensive use of vectors ( heap objects ) containng references to other heap objects Vec<Options<Rc<RefCell<_Intersection_>>>> which is not performant.

Running the examples

To view the application either create a devleopment build, or construct a static-web site as follows

Start And Run A Development Build

git clone https://github.com/martinfrances107/rust_d3_geo.git
cd rust_d3_geo/examples/ring/
cargo build
npm install
npm start

The last command "npm run start" will automatically open your default browser at http:://localhost::8080

Building A Static Site

Much better performance can be acheived by bulding a static web site and viewing that direclty.

  git clone https://github.com/martinfrances107/rust_d3_geo.git
  cd rust_d3_geo/examples/ring
  cargo build
  npm run  build

This creates a static HTML site in the dist/ directory.

To view the site you cannot just point you browser at a location of the form file:://home/user/alice/dist/index.html

By security reasons, browsers prevent HTML pages with WASM files from being viewed this way. You must host the site first.

A way forward here is to use a npm package called serve

  sudo npm install --global serve
  serve dist

If everything works you will be given a locaation to view

For example http:://localhost::3000

Benchmarking

Also rust_d3_geo_voronoi uses this library, and that project contains a benchmark which contains an exact port of a benchmark in d3-geo-voronoi . Based on that benchmark rust is 31% faster, or permits a 37% increase in throughput.

Future Multi thread support

  • rayon is rust's crate for multithread support. I have made extensive use of iterators when porting the code and rayon support the easy conversion of single threaded iterators to multithread iterators.

  • The Hashmaps - appear slow. Maybe I can get performace improvements by replacing them with B-tree collections?

Architecture discussion

There is an aspect of the design that needs review. It related to the best way to implement a doubly-linked list which has cross links between nodes. A full discusion can be found here

Coding Standard

  • Idomatic RUST, as defined by cargo clippy where possible.

  • No booleans as arguments to functions/methods, use two state enums instead. See "Reflect" as an example.

    pub trait ReflectSet {
        /// f64 or f32.
        type T;
       /// Set the projection builder to invert the x-coordinate.
        fn reflect_x_set(&mut self, reflect: REFLECT) -> &mut Self;
       /// Set the projection builder to invert the y-coordinate.
        fn reflect_y_set(&mut self, reflect: REFLECT) -> &mut Self;
    }
    

    This allow for a clearer statement of intent :-

    builder.reflect_y_set(REFLECT::Flipped);
    
  • "Type-Driven API Design" is the preferred way of constructing state machines.

    In the example below, when assembling a stream pipeline, connect() can only be called when the state is "Unconnected". The output type's STATE is "Connected".

    impl StreamTransformRadians<Unconnected> {
      #[inline]
      /// Connect this node to the next element in the pipeline.
      pub const fn connect<EP, SINK, T>(self, sink: SINK) -> StreamTransformRadians<Connected<SINK>>
      where
        SINK: Clone,
      {
        StreamTransformRadians(Connected { sink })
      }
    }
    

    The "Stream" trait is only implemented when the STATE is "Connected". By design, all code is prevented from calling line_start() or point() unless the object has been connected to another pipeline stage.

Unimplemented sections of the library.

I am trying to get a program of mine to run faster, but I want this to eventually be a true library port. So feel free to add suggestions to my todo list.

A complete list of all ported projections can be found in invert-test.rs. Out of the 15 distinct projections listed only 7 have been ported so far.

Other To-do's

Document API changes such as

  • src/projection/clip_angle_reset()
  • src/projection/clip_extent_clear()

Finally

todo.md contains a more detailed list