Giter Club home page Giter Club logo

rum's Issues

datascript -> rum/render -> should-update

I'm playing around with rum, using the idea of https://github.com/tonsky/datascript-chat - I'm omtting the async/chan part for this example. The application is rather heavily query-based, and I want to implement a :should-update mixin, which will update the components only if the queries are changed (sounds pretty obvious).
I have something like:

(def root (rum/mount (body) (.-body js/document)))

(defonce init (fn [] 
   (ds/listen! conn (fn [tx-report]
       (rum/request-render root)))))

(init)

Any updates in the database trigger a rum/request-render. Now, the 'should-update' mixin looks like:

(rum/defc comp < {:should-update (fn [o n] ... )} [args]  
    (let [q (ds/q ...)]
        ... ))

However, the 'old' state and the 'new' state are not the old and new state of the transaction (or the component as such), but rather some intermediate states of the component (so it seems to me). The old value is not the 'previous query result' or 'previous state which led to that query result'. What I'd like is to compare the previous result of the query with the new result of the query.

What's the most logic/best way to do this using rum?

Sharing state from root to all nested components

In Om, mounting the component root with om/root also allows for supplying shared state which can then be accessed by any component under the root component via om/get-shared. This allows for global data and services to be set without resorting to global state since all state is still local to the mounted component tree. This allows, for example, multiple mounts of the same app on the same page with "global" data and services independent from each other.

Would it be possible to include something similar to rum? Or are there any suggested patterns to get the same result?

Better feedback when mixing argvec/mixin order

It has happened to me a few times now that I got stuck because I put the mixin in the wrong place.

(defc x
  [a b]
  < rum/static
  [:div "bla"])

I think this is something that could be improved to give better feedback.
While throwing an exception isn't guaranteed to be correct the probability of false positives seems low (i.e. component bodies starting with a plain <).

Would you be interested in a patch that throws for the above snippet? Do you have alternative suggestions that might be less absolute?

Maybe throwing an exception is a good first step and can be revisited once there is some user feedback.

How to create materialised views from a single state atom (à la Reagent's track)?

Let's say I have the following state:

(def model 
  (atom
  {:people/by-id {1 {:id 1 :name "Bob"} 2 {:id 2 :name "Jane"}}
   :departments/by-id {0 {:name "Software" :people #{} :id 0}
                       1 {:name "Marketing" :people #{} :id 1}
                       2 {:name "HR" :people #{[:people/by-id 1] [:people/by-id 2]} :id 2}}}))

Using Reagent's track I could get a materialised view into the departments, meaning that they'd have the actual employee data and not just the idents, by first defining a function something like this:

(defn materialised-departments [atom]
  (map
    (fn [dept]
      (assoc dept :people
                  (set (map
                         (fn [emp-q]
                           (get-in @atom emp-q))
                         (:people dept)))))
    (vals (get-in @atom [:departments/by-id]))))

Then I would use the above function with r/track, to create a materialised view of the departments. My components will handle it just like a normal ratom, and react to its changes.

(let [departments (r/track model/materialised-departments atom)])

Rum (like Reagent) has cursor, but I don't know if it can be used to accomplish something like this. Honestly, I'm not sure if this issue is a support request / question, or a valid feature request. I'd be happy to hear your thoughts on this!

Using React's Perf tools to profile rum apps

It took me a while to figure out how to get React's Perf tools working within ClojureScript, and I think it'd be helpful for others to have a section in the Rum README.
I've enclosed a draft below.

I'm not sure the best way to configure Leiningen / cljsbuild / figwheel to keep the Perf tools only in development builds (or if they'll get advanced-optimizationed away) --- I bet it could be done with Leiningen's project.clj :profiles key, but there may be a better way.
I'd love if someone could chime in on that.

## Profiling

To use [React.js Perf tools](https://facebook.github.io/react/docs/perf.html) from Rum, specify the `react-with-addons` dependency in your `project.clj`, making sure to exclude any transitive react dependencies:

```clj
[rum "0.9.0" :exclusions [cljsjs/react cljsjs/react-dom]]
[cljsjs/react-dom "15.1.0-0" :exclusions [cljsjs/react]]
[cljsjs/react-dom-server "15.1.0-0" :exclusions [cljsjs/react]]
[cljsjs/react-with-addons "15.1.0-0"]
```

Then from within your program run:

```clj
(js/React.addons.Perf.start)
;;run your app
(js/React.addons.Perf.stop)
(js/React.addons.Perf.printWasted)
```

and results will be printed to the developer console.

Sharing more examples

Based on my work of porting my app to Rum, I would like to extract some of the mix-ins I have created into stand alone examples people can refer to. I would like your input on how best to share this work with the community.

Some possibilities:

  1. series of Gists that you can link to from the README if you choose
  2. pages in the Wiki (maybe a Rum Cookbook, like they are doing for Reagent)
  3. add to the examples in the project repo

Do you have a preference?

Checking for list in fn-body?

In rum.clj, there is

(defn- fn-body? [form]
  (and (list? form)
       (vector? (first form))))

Why do we need to check if form is a list here? The reason I ask is that I'm writing a macro that generates a defcs form. I can't do this because in clojure form is an ArraySeq. form appears to only be a list when defcs is expanded directly from cljs.

Using build-class

Following the 'how it all fits together' section in the docs, I'm trying to create and mount a simple React element without using defc.

I've created a fresh project with the following code:

(def a
    (rum/build-class
        {:render
         (fn [state]
             [(sablono.core/html [:div "a"]) state])}
        "a-component"))

(rum/mount (rum/element a {} nil) (.-body js/document))

Should this work? it gives me a Uncaught TypeError: Cannot read property 'call' of null ...

stacktrace:

rum.build_class.React.createClass.render    @   rum.cljs?rel=1441294910065:96
ReactCompositeComponentMixin._renderValidatedComponentWithoutOwnerOrContext @   react.inc.js:6975
ReactCompositeComponentMixin._renderValidatedComponent  @   react.inc.js:7002
ReactPerf.measure.wrapper   @   react.inc.js:13427
ReactCompositeComponentMixin.mountComponent @   react.inc.js:6423
ReactPerf.measure.wrapper   @   react.inc.js:13427

relevant code point:

      :render
      (fn []
        (this-as this
          (let [state (state this)
                [dom next-state] (wrapped-render @state)] ; <<<<<
            (vreset! state next-state)
            dom)))

I realize that part of the docs is already incorrect because build-class requires two arguments, and the label-mixin has a nested map. I'd submit a pull request about those issues but I'd like to get an example working first.

Template snippet rendered differently on client and server

I have a (rum) hiccup-ish snippet in a template, that is rendered differently by the server side code than it is in the client side code, resulting in a React warning.

Sample code
;; component in cljc
(rum/defc app []
  [:input#topics {:type "text"}])

;; server in clj
(rum/render-html (components/app))

;; client in cljs
(rum/mount (components/app) node)
Resulting warning

Warning: React attempted to reuse markup in a container but the checksum was invalid. This generally means that you are using server rendering and the markup generated on the server was not what the client was expecting. React injected new markup to compensate which works but you have lost many of the benefits of server rendering. Instead, figure out why the markup being generated is different on the client or server:

 (client) <input type="text" id="topi
 (server) <input id="topics" type="te
Some thoughts

Potential fix might be achieved by using something like https://github.com/amalloy/ordered on both clj and cljs sides, or sorted-map.

way to add custom attributes to react component?

Currently react has attributes whitelist, to be able to use custom attributes you can apply this workaround
(from http://stackoverflow.com/questions/31273093/how-to-add-custom-html-attributes-in-jsx):

Custom attributes are currently not supported. See this open issue for more info: facebook/react#140
As a workaround, you can do something like this in componentDidMount:
componentDidMount: function() {
this.refs.test.getDOMNode().setAttribute('custom-attribute', 'some value');
}
See https://jsfiddle.net/peterjmag/kysymow0/ for a working example. (Inspired by syranide's suggestion in this comment.)

How i can use this workaround with rum? i need to add webkitdirectory to file upload.

Thanks!

Problem with sibling children's keys

Today i've updated rum to 0.3.0 version and this warning appears:

Warning: Each child in an array or iterator should have a unique "key" prop. Check the render method of pic-likes. See https://fb.me/react-warning-keys for more information.

My pic-likes component looks like:

(rum/defc pic-likes < rum/static
  [{:keys [likes] :as image} user]
  (let [likes-count (count likes)
        liked (contains? likes (:id user))]
    [:a {:class-name "pic-likes"
         :href "#like"
         :on-click #(do
                      (.preventDefault %)
                      (toggle-pic-like image user))}
     (pic-likes-counter likes-count)
     (icon {:class-name "pic-likes-icon"
            :icon (if liked "favorite" "favorite-outline")})]))

If I wrap both pic-counter and icon components with with-key function and give them different keys - problem disappears. But it's kind of strange, you can see I don't iterate over these blocks. They are just siblings.

is it possible to get a rum component by data-reactid attribute value?

I'm working on a mixin that inject styles into component and sub-component
this mixin takes a function that take the component state and return a style hash-map and possibly another hash-map of type sub-element-selector -> style-function and so on
for the moment I use dommy for the sub-element-selectors and get raw HTMLelements , i would like to be able to get the value of the rum element that is mounted on in order to feed the corresponding style-fn. I really hope that it is understandable

Is there a way to get a list of all sub rum components of a rum component?

Question about state in mixins

In some cases rum\react is loosing non-default values in state between :did-mount and :will-unmount hooks in mixin. It doesn't happen if we manually transfer our state in :transfer-state hook.

The following example describes the issue https://gist.github.com/narma/46a990def0e1c6f279a2
In this code errors appear if we use dynamic count of children or we change parent elements.

Errors don't happen if we use constant count of children and don't touch parent elements because :will-unmount doesn't fire at all.

Is this expected and normal behaviour or is this a bug?

Patterns for efficient rendering of components that take callbacks

I'm using Rum to build reusable components.
These components typically don't have local state --- they're "controlled components" that take a value and on-change callback as arguments:

(rum/defc *color-picker < rum/static
  [color on-change]
  [:input {:type "color" :value color :on-change #(on-change (.-value (.-target %)))}])

Their parents use this callback to propagate new values across the app:

(rum/defc *parent < rum/static
  [parent-id color]
  [:div
   [:h1 {:style {:background-color color}} parent-id]
   (*color-picker color #(update-color-elsewhere-in-app! parent-id %))])

Semantically everything works great, but I'm having trouble with performance.
Using rum/static doesn't help because the on-change callbacks are anonymous functions --- on each render of the parent's body, a new on-change is created that is not equal to the previous on-change callback.

Are there any recommended patterns for handling this situation?
The only solution I can think of is to pass the functions in as vars rather than closures.
E.g., the above example would become:

(rum/defc *color-picker < rum/static
  [color on-change-args]
  [:input {:type "color" :value color :on-change #(apply (first on-change-args) (rest on-change-args) (.-value (.-target %))}])

(rum/defc *parent < rum/static
  [parent-id color]
  [:div
   [:h1 {:style {:background-color color}} parent-id]
   (*color-picker color [update-color-elsewhere-in-app! parent-id]))])

but this doesn't strike me as a great solution.

circular dependency between rum.core and rum.core

I am not sure I should file this here. But the dependency in core.cljs on core.clj for macros seem to confuse CIDER refactor tool.

{:reason :mranderson046.toolsnamespace.v0v3v0-alpha1.clojure.tools.namespace.dependency/circular-dependency, :node rum.core, :dependency rum.core}

derived-atom: remove watches on source atoms

When using derived-atom a watch is added to all source atoms but there's no way to remove these watches potentially causing stale watches when the derived atom is no longer needed.

I have a very rough implementation that extends the derived atom implementation by a IDisposable protocol which provides a method that will remove all relevant watches on source atoms:
https://github.com/martinklepsch/derivatives/blob/disposable/src/org/martinklepsch/derived.cljc

Watching a derived atom is broken in this implementation because it's not an atom but that's easily fixable.

Now the question, would you be interested in extending Rum's derived atom to cover this aspect?

[question] Redundant wrapping (React) components in self-referencing (Rum) components

In case multi-arity component's fns just call each other with additional arguments, each arity of such self-referencing component still creates its own React component. For instance, this is React's components hierarchy for the tree example component (notice two tree's, outer one created by (tree [:a [:b [:c :d [:e] :g]]]), and inner one - by (tree [:a [:b [:c :d [:e] :g]]] 0)):

tree component hierarchy

Such behaviour is quite logical when each arity produces slightly different components (or completely different ones), but when they simply call next arity with additional default arguments (like tree example) wrapping components seem redundant, and can cause some problems (for example, if custom did-mount is used, it would be called for each wrapping component).

So, is there any chance that in future Rum would support addition of mixins to the specific arities of multi-arity component and/or would remove redundant wrappers from produced component hierarchy of multi-arity component?

noscript is not used in react 15

Right now Rum returns [:noscript] on server when there is no DOM generated.

What it needs to return is a comment like <!--react-empty: %d-->, where %d is a data-reactid (number from the same list of numbers, also taken by sequence).

I'm not exactly sure what's the best way to implement that...

ReactDOM.unstable_batchedUpdates breaks ReactNative

Hi,

Rum works really well in re-natal w/ react-native. However, recent (unreleased) change "Do all re-renders on requestAnimationFrame in a single batch" breaks it. The same is available in react native though.

https://github.com/facebook/react-native/blob/803cb61346bcdf85d68df5cdfc1a392f78e6cac3/Libraries/react-native/react-native.js#L156

I did a workaround like this. It works. Maybe there could be a better way rum can support.

(set! js/window.ReactDOM (js/require "react-native"))

Thanks

Form controls generate React warnings

Using any HTML form controls, e.g. [:input] causes React to emit the following warnings:

Warning: Something is calling a React component directly. Use a factory or JSX instead. See: http://fb.me/react-legacyfactory

Warning: transferPropsTo is deprecated. See http://fb.me/react-transferpropsto for more information.

I spent some time tracking this down to make sure it wasn't something I was doing wrong, and I found the problem in sablono. They create a new React component to wrap each form control, and they are using createClass instead of createElement. This has been fixed in master, although I could not get my app to build using the 3.0.0-snapshot version published to clojars.

I created this issue mainly as a heads up for anyone else who runs into it, so they know that it's expected behavior and nothing is wrong. Also, maybe this can serve as a reminder to update the sablono dependency when they release a new version.

Escape attributes in server-side

By @dwwoelfel:

I noticed that attributes aren't escaped when Rum builds html with its server-side rendering.

For example,

[:div {:className "\"><script>alert('hi')</script>"}]

is perfectly safe on the cljs side, but will alert if it's rendered on the server.

Indentation support for Emacs

Problem

In emacs, the mixin macro does not get indented properly. You have to either write your components like this:

(rum/defc text [] < rum/reactive (rum/local "" ::text) ...)

Or, like this:

(rum/defc text [] < rum/reactive
  (rum/local "" ::text)
...)

This unfortunately is not pleasing to the eye at all, and can get confusing very quickly as you add more mixins to your component.

Proposed solution

The Emacs plugin, cider, allows you to specify how it should indent a function or macro. You can read about that here. Adding this to rum would be incredibly helpful!

Using external react components

This might be be just something missing from documentation, but I'm having a small issue getting external components to work. I'm using that like this:

[(.-Route js/ReactRouter) {:name "home"} ]

where window.ReactRouter.Route is the component I want to use. It throws an error from this function.

(defn normalize-element
  "Ensure an element vector is of the form [tag-name attrs content]."
  [[tag & content]]
  (when (not (or (keyword? tag) (symbol? tag) (string? tag)))
    (throw (ex-info (str tag " is not a valid element name.") {:tag tag :content content})))
  (let [[tag id class] (match-tag tag)
        tag-attrs (compact-map {:id id :class class})
        map-attrs (first content)]
    (if (map? map-attrs)
      [tag (merge-with-class tag-attrs map-attrs) (next content)]
      [tag tag-attrs content])))

Any pointers on how to get non rum components to work?

DataScript integration?

rum seems very interesting! I'd love to have components that get data from DataScript and automatically re-render on change. Do you think that is easy to do with rum?

ClojureScript 0.0-2727 and sablono 0.3.x

The latest version of sablono now depends on [cljsjs/react] which breaks rum in avanced compilation mode whilst it still depends on [com.facebook/react] (because there are two competing externs files).

Would you be interested in a pull request that updates the codebase to depend on [sablono "0.3.1"] and [cljsjs/react "0.12.2-5"] and the new foreign libraries features of ClojureScript?

This has the addiontal benefit that you no longer need both index.html and web/dev.html since all optimization levels now work with the same template.

rum.core/derived-atom without docs

rum.derived-atom/derived-atom has a nice docstring, but it doesn't transfer over to rum.core/derived-atom, which appears to be undocumented. A fix could be to move the docstring to the def in rum.core.

[question] How to pass state across tree

I have one single atom for state.
Some components may want to watch some parts of this atom.
But their parents pure components without data.

For example: page header contain user control. Header doesnt have any data from state, but user control have. In react I pass this state by context. And wrap user control in container that watch data in state and pass as props to user control. I can do it with rum, but I want to know is this best way?

Also my question not about only state atom, but about all things components may want deep in tree.
In my case is state and dispatcher (chan).

Argument Restructuring / Variadic Arguments

(rum/defc input1 < rum/reactive [ref & [attrs]]
  [:input (merge {:type "text"
                  :value @ref
                  :on-change #(reset! ref (.. % -target -value))}
                 attrs)])

 (rum/defc input2 < rum/reactive
  ([ref] (input ref nil)
  ([ref attrs]
   [:input (merge {:type "text"
                   :value @ref
                   :on-change #(reset! ref (.. % -target -value))}
                  attrs)]))

I think the two defc calls above don't work. input1 causes:

WARNING: Use of undeclared Var fcc.app/& at line 12 /Users/martin/.boot/tmp/Users/martin/projects/fun-club/ta/-p9hf6h/fcc/app.cljs

input2 causes

WARNING: Use of undeclared Var fcc.app/attrs at line 16 /Users/martin/.boot/tmp/Users/martin/projects/fun-club/30i/-p9hf6h/fcc/app.cljs

Render on atom change outside of event handler, like Reagent

Hello,

In the Readme file, it says:

On higher level, Rum comes with already-built types of components which emulate behavior found in Om, Reagent and Quiescent"

It makes me think that Rum can already work exactly like Reagent, but I think that is not the case because one feature that Reagent can do that Rum cannot do out of the box is the following, as referenced from https://reagent-project.github.io/news/reagent-is-async.html:

... proper batching of renderings even when changes to atoms are done outside of event handlers (which is great for e.g core.async users).

So is there a plan to match at least that feature of Reagent?

Example piece of code below shows an input that can only accept numbers. Typing in alphabet keys will not render the alphabets at all in the input in Reagent, but does render for a split second with the Rum code below.

(def text (atom ""))
(def my-chan (chan))

(rum/defc num-input < rum/reactive []
  [:input {:value (rum/react text) :on-change
    (fn [e] (put! my-chan (string/replace (.-value (.-target e)) #"[a-zA-Z]*" "")))}])

(go
  (while true
    (reset! text (string/replace (<! my-chan) #"[a-zA-Z]*" ""))))

(rum/mount (num-input) js/document.body)

@ajchemist has already suggested a solution by using (rum/request-render component) but must be inside the event handler. However does that match the behaviour of Reagent?

Kind regards,
Robin

Provide a way to exclude cljsjs/react

I want to use my own React build. I added

:dependencies [[rum "0.2.6"]]
:exclusions [cljsjs/react]

to my project.clj but it can't compile Rum now giving this error:

clojure.lang.ExceptionInfo: No such namespace: cljsjs.react at line 1 file:/Users/petrus/.m2/repository/rum/rum/0.2.6/rum-0.2.6.jar!/rum.cljs {:tag :cljs/analysis-error, :file "file:/Users/petrus/.m2/repository/rum/rum/0.2.6/rum-0.2.6.jar!/rum.cljs", :line 1, :column 1}

Is there another way for doing this?

(We were talking about this in chat, but probably here is a better place for discussion).

Order of arguments to defc

defc is different from almost all of the other def-something forms seen in the Clojure world in that its first argument is not the name of the thing being defined. Would you consider changing the semantics of it to be (defc name mixin(s) args & body) instead? I'm guessing your intention was to avoid having two vectors following each other, which might look confusing to a lot of people, but the way it works right now breaks syntax highlighting with most editors.

This is obviously a totally breaking change for a rather petty reason, but I figured that since the project is still so young this might not be such a big deal.

Argument destructuring is not working properly

Code like this:

(rum/defc Label [{:keys [id text] :as item}]
  (js/console.log (pr-str item) (pr-str text))
  [:div {:key id} (str text " ") [:span.badge id]])

outputs this to console:

 {:keys [2 "Two"], :as {:id 2, :text "Two"}} nil

Reasons for Rum

I really like rum. Thanks, @tonsky! You have good taste.

Consider adding your reasons for making Rum to the README, to help others decide when and why to choose use Rum over Om, Reagent or Quiescent.

What is the idiomatic way of managing state?

In reagent you can return the render function instead of directly returning the component description.1
So you can do this, for example:

(defn timer-component []
  (let [seconds-elapsed (reagent/atom 0)]     ;; setup, and local state
    (fn []        ;; inner, render function is returned
      (js/setTimeout #(swap! seconds-elapsed inc) 1000)
      [:div "Seconds Elapsed: " @seconds-elapsed])))

I'm wondering what is the corresponding solution in rum.

React keys warning when component body within let

Using

[rum "0.2.7" :exclusions [cljsjs/react]]
[cljsjs/react-with-addons "0.12.2-7"]

Rum causes a React keys warning when a component render body is within a let.
The minimal test case below:

(defn raw-sablono
  []
  (sablono.core/html
   [:div
    [:span "A"]
    [:span "B"]]))


(defn let-sablono
  []
  (sablono.core/html
   (let [x 1]
     [:div
      [:span "A"]
      [:span "B"]])))


(rum/defc raw-rum
  []
  [:div
   [:span "A"]
   [:span "B"]])


(rum/defc let-rum
  []
  (let [x 1]
    [:div
     [:span "A"]
     [:span "B"]]))


(rum/defc app
  []
  [:div
   (raw-sablono)
   (let-sablono)
   (raw-rum)
   (let-rum)])


(.render js/React (app) (.getElementById js/document "container"))

Causes the following warnings in the JavaScript console:

Each child in an array should have a unique "key" prop. Check the render method of app. See http://fb.me/react-warning-keys for more information.
Each child in an array should have a unique "key" prop. Check the render method of let-rum. See http://fb.me/react-warning-keys for more information.

I'm confused by both warnings since none of this code is using sequences --- everything should compile into direct React calls without intermediate arrays.

I've tried debugging by expanding rum's output in a Clojure REPL:

(macroexpand
   '(rum/defc app
      []
      [:div
       [:span]
       [:span]]))

but it blows up:

1. Unhandled java.lang.IllegalArgumentException
   Syntax error at ([] [:div [:span] [:span]])

                       rum.clj:   25  rum/parse-defc
                       rum.clj:   31  rum/-defc
                       rum.clj:   54  rum/defc

React 0.13 warning about mutated props

I could just be doing something wrong but Rum seems to be running afoul of the change in React 0.13 to immutable props. See here and here.

(defonce app-state (atom {:text "Hello World!"}))

(rum/defc app < rum/cursored rum/cursored-watch [app-state]
          [:h1 (:text @app-state)])

(defn main []
  (rum/mount
    (app app-state)
    (. js/document (getElementById "app"))))
Warning: Don't set .props.:rum/state of the React component <app />. Instead, specify the correct value when initially creating the element or use React.cloneElement to make a new element with updated props.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.