rid3

by gadfly361

gadfly361 / rid3

Reagent Interface to D3

129 Stars 6 Forks Last release: Not found MIT License 65 Commits 6 Releases

Available items

No Items, yet!

The developer of this repository has not created any items for sale yet. Need a bug fixed? Help with integration? A different license? Create a request here:

rid3

Rid3: Reagent interface to d3. Pronounced like the word 'ride'.

basics

examples

clojure meetup slides based on v0.1.0-alpha-3.

To use rid3, add the following to the

:dependencies
vector in your project.clj file:
[rid3 "0.2.2"]

Using an older version?

The Problem

In my experience, there is a lot of boilerplate involved when trying to get reagent (i.e., react) and d3 to play nicely together. The crux of the problem is you only want to append containing g tags (or static elements) to the DOM during reagent's

component-did-mount
lifecycle method and not during the
component-did-update
lifecycle method. However, more often than not, you want d3 to modify the stuff contained in the g tag in the same manner whether or not the component just mounted or just updated.

Rid3's Solution

Rid3 exposes a single reagent component,

viz
, with the aim to make 80% of what you'll likely want to make with d3 easier (bar charts, pie charts, scatter plots, etc.). Rid3 attempts to achieve this by making containing g tags for you. The benefit of doing this is that you can now use the same function in the did-mount and did-update lifecycle methods. However, rid3 goes a step further and will default the did-update lifecycle method to whatever you supply as the did-mount lifecycle method.

Viz Component

The

viz
component takes a hash-map of the following:

| key | type | default | required? | |------------------|--------------------|-------------------------------------------------|-----------| | :id | string | | yes | | :ratom | reagent.core/atom | | yes | | :svg | svg | | yes | | :main-container | main-container | | no | | :pieces | [ piece ] | | no | | :class | string | | no |

  • an
    :id
    is required to differentiate between different rid3's
  • a
    :ratom
    can be a reagent atom, reagent cursor, or a re-frame subscription. This should be used to store all of the data relevant to your rid3.

:svg

Where an svg hash-map looks like:

| key | type | default | required? | |-------------|-----------------------|-----------|-----------| | :did-mount | (fn [node ratom] ...) | | yes | | :did-update | (fn [node ratom] ...) | did-mount | no |

:main-container

Where a main-container hash-map looks like:

| key | type | default | required? | |-------------|-----------------------|-----------|-----------| | :did-mount | (fn [node ratom] ...) | | no | | :did-update | (fn [node ratom] ...) | did-mount | no |

:pieces

And where :pieces is a vector of piece hash-maps. There are four kinds of piece hash-maps:

:container
for when you want to group elem or elem-with-data pieces under the same g tag.

| key | type | default | required? | |-------------|-----------------------|-----------|-----------| | :class | string | | yes | | :did-mount | (fn [node ratom] ...) | | yes | | :did-update | (fn [node ratom] ...) | did-mount | no | | :children | [ piece ] | | no |

:elem
for when you want to add an element like a text or a circle.

| key | type | default | required? | |-------------|-----------------------|-----------|-----------| | :class | string | | yes | | :tag | string | | yes | | :did-mount | (fn [node ratom] ...) | | yes | | :did-update | (fn [node ratom] ...) | did-mount | no |

:elem-with-data
for when you want to add a series of elements that are joined to a dataset.

| key | type | default | required? | |------------------|-----------------------|-------------------------------------------------|-----------| | :class | string | | yes | | :tag | string | | yes | | :did-mount | (fn [node ratom] ...) | | yes (1) | | :did-update | (fn [node ratom] ...) | did-mount | no | | :gup | gup-hash-map | | yes (1) | | :prepare-dataset | (fn [ratom] ...) | (fn ratom) | no | | :key-fn | (fn [d i] ...) | | no |

  • :elem-with-data
    pieces expect what is returned by the
    :prepare-dataset
    function to be a JavaScript array. E.g.
    [1, 2, 3]
    or
    [{"color": "blue"}, {"color": "green"} ... ]
  • If

    :prepare-dataset
    is not provided, then (as a default)
    :elem-with-data
    will convert whatever is stored in the
    :dataset
    key of your
    ratom
    to a JavaScript array.
  • Individual datums (often referred to as "d") of the JavaScript array are passed to the anonymous functions that can be used to set properties of each

    :elem-with-data
    element. E.g.
    (.attr node "color"
    (fn [d] (goog.object/get d "color")))
    • (1) You can use either
      :did-mount
      and
      :did-update
      or
      :gup
      . You cannot mix.
    • If you choose to use
      :gup
      , then you can explicitly set attributes and add transitions in the
      :enter-init
      ,
      :enter
      ,
      :update
      , and
      :exit
      parts of the general update pattern.
    • A
      gup-hash-map
      looks like:
{:enter-init  (fn [node ratom] ...)
 :enter       (fn [node ratom] ...)
 :update      (fn [node ratom] ...)
 :exit        (fn [node ratom] ...)}
  • Note: only use
    :enter-init
    if you want a special enter transition when the vizusualiztion first mounts, otherwise, you can ignore it, and just use
    :enter
    ,
    :update
    and
    :exit
    .

:raw
for when you want to either trigger some side-effect or have an escape hatch from the rid3

| key | type | default | required? | |-------------|------------------|---------|-----------| | :did-mount | (fn [ratom] ...) | | yes | | :did-update | (fn [ratom] ...) | | yes |

Joy Rid3 (Minimal Example)

Just so you can get a sense of a

viz
component, let's create a minimal example ... a circle with text on top of it.

Add rid3 to your project

Add the following to the

:dependencies
vector of your project.clj file.
[rid3 "0.2.2"]

Require rid3 in your namespace

(ns foo.core
  (:require
   [rid3.core :as rid3 :refer [rid3->]]))

Create an svg

(defn viz [ratom]
  [rid3/viz
   {:id    "some-id"
    :ratom ratom
    :svg   {:did-mount (fn [node ratom]
                         (rid3-> node
                                 {:width  200
                                  :height 200
                                  :style  {:background-color "grey"}}))}
    }])

Which will result in the following:

Note: All viz components need to provide a ratom. All relevant data for the component should be stored here. If anything changes in this ratom, then rid3 will trigger a re-render of the viz component for you.

You probably noticed that rid3 added a g tag with the class

.rid3-main-container
inside of your svg. This is where rid3 will place all of your pieces.

You probably also noticed the use of

rid3->
. This is just syntactic sugar to set attributes with familiar hiccup-like attribute maps. The above is the same as doing the following:
(defn viz [ratom]
  [rid3/viz
   {:id    "some-id"
    :ratom ratom
    :svg   {:did-mount (fn [node ratom]
                         (-> node
                             (.attr "width" 200)
                             (.attr "height" 200)
                             (.style "background-color" "grey")))}
    }])

Add a circle

(defn viz [ratom]
  [rid3/viz
   {:id    "some-id"
    :ratom ratom
    :svg   {:did-mount (fn [node ratom]
                         (rid3-> node
                                 {:width  200
                                  :height 200
                                  :style  {:background-color "grey"}}))}
    ;; ATTENTION \/
    :pieces
    [{:kind      :elem
      :class     "backround"
      :tag       "circle"
      :did-mount (fn [node ratom]
                   (rid3-> node
                           {:cx 100
                            :cy 100
                            :r  50}))}]
    }])

Which will result in the following:

Please note, that there is no

.append
method in our did-mount function! Rid3 created a containing g tag and placed the circle inside of it for us. The node that is passed to the did-mount function is the circle itself. You can confirm this by looking at where the attributes from our did-mount function are added.

Add text

(defn viz [ratom]
  [rid3/viz
   {:id    "some-id"
    :ratom ratom
    :svg   {:did-mount (fn [node ratom]
                         (rid3-> node
                                 {:width  200
                                  :height 200
                                  :style  {:background-color "grey"}}))}
    :pieces
    [{:kind      :elem
      :class     "backround"
      :tag       "circle"
      :did-mount (fn [node ratom]
                   (rid3-> node
                           {:cx 100
                            :cy 100
                            :r  50}))}
     ;; ATTENTION \/
     {:kind      :elem
      :class     "foreground"
      :tag       "text"
      :did-mount (fn [node ratom]
                   (rid3-> node
                           {:x                  100
                            :y                  100
                            :text-anchor        "middle"
                            :alignment-baseline "middle"
                            :fill               "green"
                            :font-size          "24px"
                            :font-family        "sans-serif"}
                           (.text "RID3")))}]
    }])

Which will result in the following:

RID3

Note: the order of pieces matters. If we reversed the order of our text and circle, we wouldn't be able to see the text (because it would be behind the circle).


Don't forget to check out the following:

basics

examples

License

The MIT License (MIT)

Copyright © 2017 Matthew Jaoudi

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

We use cookies. If you continue to browse the site, you agree to the use of cookies. For more information on our use of cookies please see our Privacy Policy.