On D3 Components

213 visualizações
Pular para a primeira mensagem não lida

Pedram Emrouznejad

não lida,
21 de mai. de 2014, 17:09:3921/05/2014
para d3...@googlegroups.com
Hi All,

I've written up an article on component architecture using D3: http://ag.svbtle.com/on-d3-components

This is an extension of the reusable chart article in some ways.

I would love any feedback/comments/critiques you may have on this.

Thanks,
Pedram


Kai Chang

não lida,
21 de mai. de 2014, 18:51:0321/05/2014
para d3...@googlegroups.com
This is a great overview with clear examples and engaging to read.

--
You received this message because you are subscribed to the Google Groups "d3-js" group.
To unsubscribe from this group and stop receiving emails from it, send an email to d3-js+un...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Mike Bostock

não lida,
21 de mai. de 2014, 21:48:0921/05/2014
para d3...@googlegroups.com
Nice writeup!

Mike

marc fawzi

não lida,
22 de mai. de 2014, 13:04:0122/05/2014
para d3...@googlegroups.com
I'm with you on the selection.call(component) pattern but you've lost me on the d3 dispatch and rebind for inter-component eventing. Could you delve a little more into that with some code so we can be clear what you're saying (without too much thinking... if possible)?


On Wed, May 21, 2014 at 6:48 PM, Mike Bostock <mi...@ocks.org> wrote:
Nice writeup!

Mike

Mike Bostock

não lida,
22 de mai. de 2014, 13:22:0222/05/2014
para d3...@googlegroups.com

Alex Mastro

não lida,
22 de mai. de 2014, 13:38:0422/05/2014
para d3...@googlegroups.com
As a new user of D3, this kind of write-up is great for elaborating on D3's design philosophy.

marc fawzi

não lida,
22 de mai. de 2014, 14:21:5322/05/2014
para d3...@googlegroups.com
Thanks Mike. 

d3.dispatch is straight forward (I've been doing it with Backbone Event because it's there in my apps) but the way rebind is used with dispatch makes it more appealing... as in component.on

Pedram, I think the article could use some code examples in the Event Communication section and then it would be complete!


On Thu, May 22, 2014 at 10:22 AM, Mike Bostock <mi...@ocks.org> wrote:

--

Nick Guenther

não lida,
22 de mai. de 2014, 14:55:5222/05/2014
para d3...@googlegroups.com
I loved the reusable chart article, and am excited for more.

> Take for example the following code which spawns 3 boxes for a filter
sentence: ...

This section is confusing to me because I equate

|> d3.select('ul')
> .selectAll('li')
> .data([{...}, {...}, {...}])
> .enter()
> .append('li')

with| "render". Is that wrong? After all, it makes new pixels appear on my screen.

It would be more understandable if you filled in actual elements in .data() and
made all the code on the page available in a runnable package.


> In reusable components, we take the idea of joins to the max by also
using them
> for the _generation_ of the structure of the component itself
(typically, |.data([0])|.

What does this mean? Why [0]? If you have no data, why not just an empty
data element?
My understanding of d3 is that you define, as you say, a render
sequence, and then
only when you have data available you call .data(), and if you call
.data([]) the elements should vanish.

I suspect I'm totally missing the point.

> One effect this gives rise to is called the “re-render everything
principle”.
> Because D3 calculates the difference between the current state and the
> desired state, then selectively applies operations (if at all), “re-render
> everything” suddenly becomes much more inexpensive (but not free).

Please elaborate on this. As someone still learning d3, it has been
confusing to me what a "render" is.
Is a "render" when d3 updates the DOM? Is it when the browser takes the
DOM and redraws the pixels?
In order to get our plots to change with new data, my project group fell
into the trap of writing a redraw()
and calling it in a loop. It felt wrong, but I did not know enough to
explain why. I also want to know d3's diffing
algorithm; if we insert two elements in different spots near the middle
of a 100,000 element list, does d3 scan the
entire list looking for them? And when it finds them, what does it store
and how much work does that trigger?


I am glad to see writeups like this coming along. I get the impression
D3 has been mostly learned by example,
and the people who understand the philosophy get it and the people who
don't are stumped. Bridges like these are critical.

--
-Nick Guenther
4A Stats/CS
University of Waterloo

Pedram Emrouznejad

não lida,
25 de mai. de 2014, 21:21:0825/05/2014
para d3...@googlegroups.com, ngue...@uwaterloo.ca
Thanks for the feedback everyone, much appreciated.

@marc: You're right about the event communication seeming a bit vague. Mike linked to an example of using d3.dispatch, but applied in a slightly different technique. It's best to illustrate by a simple example how this would look in a re-usable component context. Consider again the skeletal template at the start of the article. Enhancing it with the most basic eventing it would look like:

function createChart() { var width = 720 , height = 80
, dispatch = d3.dispatch("foo", "bar") // added this line function chart() { /* generate chart here, using `width` and `height` */ } /* getter-setter accessor methods: */ chart.width = function(value) { if (!arguments.length) return width width = value return chart } chart.height = function(value) { if (!arguments.length) return height height = value return chart } return d3.rebind(chart, dispatch, 'on') // changed this line }


Instead of returning "chart", we now return "d3.rebind(chart, dispatch, 'on')". And inside the component itself, where appropriate, you'd call "dispatch.foo()". This means in the application code, the invocation can now look like:

var myChart = createChart() .width(720) .height(80)
.on("foo", onFoo)
.on("bar", onBar)
d3.select(".chart").call(myChart)

You'll generally want to avoid too much hierarchy, but in a "D3 app" (or otherwise) you will inevitably need to stack some D3 components vertically. Which is what the rest of that section is about:

  • Basic example ("the basic practice of proxying dispatchers"): if you have component A which fires event X and component B which fires event Y, and you wish to make component Z which subsumes A and B, then in Z you can listen to event X on A, and re-trigger a new event X on Z (and likewise with B/Y). 
  • Advanced example ("native equivalent for 'rebinding’ events"): the former approach requires some manual work - instead you can write you're own utility which is analagous to "rebind" (i.e. just as you have a convenient method for copying methods from a source to a target, you can write a "redispatcher" which copies events from a source to a target). I'm not sure if this is something that could belong inside D3 itself (Mike?), but it's pretty trivial to do.

If the above is not clear, I hope to check in some actual functional components soon to illustrate better.

@Nick, to answer your question on the "idempotent components" section, yes it's a completely different technique than binding data. .data([0]) doesn't bind no data, it binds exactly one element. The reason for using that is because a component often has structural elements independent of the data. For example, in a calendar component the number of elements representing the days is many, but there is only one element for the name of the month, and forward/backward buttons. To generate those elements , you could have more lifecycle functions for the component (i.e. start and update), then generate the structure only in the start. However, we'd lose the simplicity of the rubber-stamping effect. We could have a single update function still and use some if guards to not hurt performance:

if (!titleElement) createTitleElement()

But we'd be tracking more state. A more declarative solution would be something along the lines of:

d3.select(root)
  .selectAll('.title')
    .data([0])
    .enter() 
  .append('div')
    .each(createTitleElement)

Then no matter how times your function is called, when it passes over that, the structural element is only created once.

There is a real example of this you can check out by Jason Davies here.

Regards,
Pedram

marc fawzi

não lida,
26 de mai. de 2014, 12:43:5126/05/2014
para d3...@googlegroups.com, ngue...@uwaterloo.ca
With respect to dispatcher/re-dispatcher (event forwarding) I think the whole thing there maybe too convoluted. Why not adopt pub-sub for inter-component communication? a component simply dispatches an event into a namespace and some event name (channel and topic metaphors) and if another component needs to react to that then it registers a listener to listen to that event on that namespace. Not sure why the need to dispatch on the publisher, listen on the publisher, then re-dispatch on the subscriber. There are many minimalistic pub-sub implementations in JS.

Also, in your model, what is onFoo in the code you gave (below)

var myChart = createChart() .width(720) .height(80)
.on("foo", onFoo)
.on("bar", onBar)


Is it an event handler function that executes an action in response to an event or is it a re-dispatcher?






--

marc fawzi

não lida,
26 de mai. de 2014, 13:05:5726/05/2014
para d3...@googlegroups.com, ngue...@uwaterloo.ca
Check this out for EventEmitter in browser


Separate from the above, what I was expecting to see in the .on for a component is something like:

var myChart = createChart() .width(720) .height(80)
.on("foo", function(e) { this.someMethod(e.data)})
not .on("foo", some_handler_function_not_beloinging_to_the_component)

I guess I need to understand what problem(s) you're trying to solve at a high level when it comes to events


Odanga Madung

não lida,
28 de mai. de 2015, 08:05:2528/05/2015
para d3...@googlegroups.com, mi...@ocks.org
Hi Mike,

How do I make the axis in that dispatch example change dynamically with every transition? 

Best 
Responder a todos
Responder ao autor
Encaminhar
0 nova mensagem