Giter Club home page Giter Club logo

d3-selection's Introduction

d3-selection

Selections allow powerful data-driven transformation of the document object model (DOM): set attributes, styles, properties, HTML or text content, and more. Using the data join’s enter and exit selections, you can also add or remove elements to correspond to data.

Resources

d3-selection's People

Contributors

curran avatar dependabot[bot] avatar fil avatar hugo-trentesaux avatar infinitesunrise avatar jimkang avatar likev avatar lyrachord avatar mbostock avatar nathanbowser avatar severo avatar stof avatar tomwanzek avatar tonysherbondy avatar wrgoldstein avatar xy2i avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

d3-selection's Issues

Code structure: extending d3 object

How about changing the exports from selection.js to:

module.exports.selection = Selection;
module.exports.select = Selection.select;
module.exports.selectAll = Selection.selectAll;

That way when creating a standalone you could just extend the d3 object with any required modules like:

var extend = require('xtend/mutable') // or something similar
var selection = require('./lib/d3/selection')
var event = require("./lib/d3/event")
var d3 = {}

extend(d3, selection, event)

module.exports = d3

knowing each module exported methods / properties that could just extend the core d3 module could make it easier to maintain something like standalone.js

Uncertain `this` contexts.

For the normal case, say selection.attr, the this context is the DOM node. The arguments are the datum (d) and index (i), followed by the parent datum and index, and so forth.

But what about for selection.data?

The value function—that returns an array of data—is invoked once per group, not once per node. So the this context could conceivably be (1) the parent node of the group or (2) the group itself (an array of nodes). The latter is what D3 3.x and below do, which arguably has the nice property that you can compute some dynamic data from the nodes. But really in that case, wouldn’t it make more sense to use selection.datum and just bind the data directly?

The key function has a similar issue, except keys are necessarily computed after the data values, so there’s another option (3) the values array (which is what D3 3.x does). But I can’t think of any time where I’ve depended on this behavior, so it probably makes more sense to switch both of them to the group’s parent node.

Should EnterNode implement appendChild and insertBefore?

If EnterNode provided custom implementations of these methods, we wouldn’t need an explicit EnterSelection implementation. But, this would require each EnterNode to have a reference to the group’s parentNode, and for the EnterNode to know which index to set in the update group upon creation. And it would probably prevent enter.select from working…

Should the selection.groups array be public? SingletonSelection and EmptySelection?

It might be good to make the groups field private (say, renaming it to _).

This would allow us to have specialized implementations of singleton and empty selections. Actually, come to think of it this wouldn’t work because joining an empty selection to data makes it non-empty. We could only do this if we went back to selection.data returning a new selection, thereby allowing selections to be immutable. (Which would preclude the new update-in-place feature.)

For debugging, the private field would still be visible.

For iteration, we could provide public selection.groups and selection.nodes methods. The latter would return a flat array of nodes (or perhaps just the first group of nodes, for symmetry with selection.data?). We already have selection.each, but I can see these other methods being useful sometimes.

The “active” selection?

Related d3/d3#2246. With ES6 arrow functions on the horizon, using this to represent the current selected node has diminishing usefulness. We should consider one of these options:

  1. Setting the current node as a global (node), similar to the event global.
  2. Tracking the current node internally and exposing an activeSelection (selectActive?) method.

The latter approach is along the lines of d3/d3-transition#15, which proposes an activeTransition method for accessing the active transition on a given element. We could restrict activeTransition to only apply to the current node, in which case you wouldn’t need to pass it a node, and you wouldn’t need to expose a global node, either.

The active selection should probably maintain the same grouping structure as the originating selection, rather than being limited to a flat selection. Although, I’m not sure this is a requirement.

I also wonder if having an activeSelection method would eliminate the need to pass indexes to selection methods. Well, you often still want to access the index as implicit data. So perhaps we do want to expose a node and index global, even if we also provide an activeSelection method. And if we are exposing these globals, then activeSelection() is simply shorthand for select(node) which isn’t especially useful… unless preserving the nested structure of the active selection is useful, in which case the two are slightly different.

Then the question is whether we want to also expose the active parent nodes and indexes as globals? And how?

Implement selection.ons.

The name is slightly awkward (multi-value map variant of selection.on). We could rename selection.on to selection.event, I suppose.

Implement d3.mouse, d3.touch, d3.touches?

I’m not 100% convinced these should be part of the d3-selection package, but them seem essential to make selection.on (#1) useful, and are small enough that I don’t think they belong anywhere else.

Open questions.

Do we want a full hierarchy, or should we limit selections to a single level of nesting (groups + nodes) as in 3.x? The primary benefit of the full hierarchy is that it allows us to track all parent data and indexes to functions {d, i, p, j, …}. These can either be passed to selection functions or exposed as globals during function invocation.

How useful is it to have access to the parent data and index? The parent index alone provides a way to access the parent data, albeit awkwardly. Not exposing parent data encourages parent data to be pushed down to children, which (while perhaps a pain) has a potential benefit in that nodes can be reselected with their data already bound, rather than needing to reselect the parent and then the child to restore the full context.

I seem to recall some cases where having access to the parent data simplified the creation of small multiples or a scatterplot matrix. http://bl.ocks.org/mbostock/4063663

Do we want to pass all parent data and indexes to functions? Or would it be better to use fixed arguments, say {d, i}? In the latter case, is there a third argument equivalent to {d, i, array} in array.forEach? Would the last argument be the array of data for group (which is awkward because that array isn’t typically materialized)? Or would it be the array of elements? Or would it be the parent node’s data?

Proposal: smarter appending to enter selection

In 3.x it's very common to do something like this:

d3.selectAll("div.myclass")
    .data(myData)
    .enter()
    .append("div")
    .classed("myclass", true)

For brevity I'm assuming we only care about the enter selection, but it's true in general: most of the time we append elements that match the original selector. We have to duplicate the element name and class.

It would be very convenient to have the following be equivalent to the above:

d3.selectAll("div.myclass")
    .data(myData)
    .enter()
    .append()

Currently append always takes arguments, and that would override the selector given. I'm not sure if this proposal is feasible in general, with nested selections (now arbitrary deep I hear?) and complex selectors. But hey, no harm in asking right?

How to access the current group inside a selector?

In enter.select, the selector wants access to the current group for two reasons: (1) to set the corresponding slot in the update group when the enter node is materialized and (2) to find the insertBefore reference node based on the next non-null node in the update group. (See selectorUpdateOf.)

However, selectors only currently have access to the current node (or parent node, as in the case of the enter selection) and the data stack (ancestor data and index).

Some options:

  1. The EnterSelection could use a private global which references the current group.
  2. The current group could be exposed as a public global (similar to d3.event).
  3. The selector could reach the current group using the indexes in the passed arguments.
  4. The current group be the last argument (to all functions, presumably).

Data join without DOM?

I don't know if this is the right time or place to ask this, but... Have you ever considered to generalize the data join mechanism to work with something other than the DOM? I'm thinking about using it with 3D objects in three.js, markers in Leaflet, or even canvas, by specifying custom functions to handle creation, update and deletion of stuff.

Do you think it would be feasible / useful?

npm publish

Absolutely no rush, but whenever you have time, can you build and publish the latest to NPM?

How should Node require avoid the `d3.event` global?

Event handling requires setting d3.event, which therefore depends on a d3 global. That global is defined in standalone.js. However, it would be nice if the d3 object did not have to be global, as when the library is loaded via RequireJS.

Related, the standalone.js should do something like this:

if (typeof define === "function" && define.amd) define(d3);
else if (typeof module === "object" && module.exports) module.exports = d3;
global.d3 = d3;

… except of course that doesn’t work with Browserify because it defines module.exports.

Tests.

  • selection
  • select
  • selectAll
  • selection.select
  • selection.selectAll
  • selection.attr
  • selection.classed
  • selection.style
  • selection.property
  • selection.text
  • selection.html
  • selection.append
  • selection.remove
  • selection.data
  • selection.enter
  • selection.exit
  • selection.datum
  • selection.filter
  • selection.sort
  • selection.order
  • selection.on
  • selection.dispatch
  • event
  • mouse
  • touch
  • touches
  • selection.each
  • selection.call
  • selection.empty
  • selection.nodes
  • selection.node
  • selection.size
  • namespace
  • namespaces

Implement selection.data.

Note that the API is slightly different than 3.x, and that this will require some d3_Map replacement (which I think should only be available internally and not part of the standalone public API).

Listeners must recompute ancestor data.

Currently we only recompute the target element’s data, but we should recompute the data for all ancestors, too. This means that listeners need to capture not the data, but the parent nodes and indices.

Should selections be immutable? Or should selection.data modify in-place?

I can see arguments to both sides. Normally I’d lean towards immutability, but there’s already the case in D3 3.x where enter.select modifies the update groups in-place, and this is quite convenient. It feels like D3 3 (and prior) is sort of in a middle ground where enter.select modifies in place but selection.data returns a new selection. It would be better to either go for full immutability, or go for mutability and look for places where we can make the API more convenient.

(Also, selection.sort is another place where selections are mutable. So for immutability, this would also need to return a new selection.)

exit.remove should clear removed nodes.

For symmetry with enter.select moving nodes from enter to update, and to ensure that exited nodes are garbage-collected, selection.remove should return a new selection containing the removed nodes while removing it from the current selection.

Memoize Accessors

Touching the DOM can obviously be a major bottleneck, so I think it might be a good idea to look into memoizing all D3 accessors for additional performance improvements. In a higher-level wrapper, I use the simple technique of using the getter of each accessor to compare with the target value and only make the change if they are different. Since D3 components are used as "stamps" this scenario is very common. That is, in subsequent renders, most modifications should already be set and so it should really amount to a noop if nothing has changed:

function component(data) {
  once(this)
    ('li', data)
      .attr('tabindex', -1)
      .classed('.active', d => d.active)
      .text(d => d.label)
}

I don't have comprehensive stats, but running the following quick tests shows that resetting a property to it's own value is not completely trivial - ~0.2ms vs 0ms (setTimeout is used to avoid compiler loop invariant optimisations):

for (var i = 0; i < 100; i++) time(i*100, perf(d => input.value))
for (var i = 0; i < 100; i++) time(i*100, perf(d => input.value = 'foo'))

Open a new tab, open the console, and paste the following in the address bar to run:

data:text/html, <head><script src="https://rawgit.com/utilise/utilise/master/utilise.js"></script><body><input><script>input = raw('input'); for (var i = 0; i < 100; i++) time(i*100, perf(d => input.value))</script>
data:text/html, <head><script src="https://rawgit.com/utilise/utilise/master/utilise.js"></script><body><input><script>input = raw('input'); for (var i = 0; i < 100; i++) time(i*100, perf(d => input.value = 'foo'))</script>

The if (current !== target) approach should work for most accessors from what I can see, except for .style. Just checking the current value in that case can be costly. One option would be to store all the values set in a hidden __style__ object property and then check against that. However, that may restrict interoperability (i.e. if you set inline styles another way, it may lead to unexpected results in subsequent D3 set operations). It's not common to mix different solutions though, so users may just have to be cautious of that. Or an opt-in/out parameter could be added. Or you could just leave .style as-is, since anyone who cares about performance probably isn't setting inline styles across their application (using classes is perhaps more likely).

Happy to open a PR if you think this would be a good idea..

Publish current version with fixed selection/classed

The current 0.6.0 package published to NPM has a version of the selection/classed.js module that uses setConstant. This doesn't work because it looks like it's since been renamed to classedConstant. In master, it uses classedConstant instead, and thus, is fine. Can you publish this fix to NPM?

d3.selection() should return the root selection

Currently d3.selection is the Selection constructor, which is weird, because you should never say new d3.selection(root, depth). The constructor should theoretically be private…

On the other hand, it’s nice to be able to say o instanceof d3.selection. And it’s critical that we allow other modules (such as d3-transition and d3-selection-multi) to extend d3.selection.prototype.

So probably we should do what D3 3.x does for d3.transition, which is:

  • d3.transition is a function that returns a root transition
  • d3.transition.prototype is set to Transition.prototype
  • Transition is a private internal constructor

Setting NodeList `length` in strict mode throws TypeError

I'm updating an app from d3 3.5.x and ran into an issue using selections in strict mode.

My code is a basic selection / data binding: selection.selectAll('div').data(data). Data is an Array and the selection length is equal to the data length (4).

This worked correctly in 3.5.x with strict mode. Now I'm getting a TypeError in the data method when it tries to set update.length = dataLength (src).

Here's a reproducible test case, but the basic gist is this:

var nl = document.querySelectorAll('div');

console.log(nl.length); // 4
nl.length = 4;
console.log(nl.length); // 4, doesn't change length but silently swallows error

'use strict'
nl.length = 4
// TypeError: Cannot set property length of #<NodeList> which has only a getter

selection.data() should return all the data, not just the first group’s

It’s kind of weird either way, but it feels like selection.data only returning the first group’s data makes selection.data (with no arguments) only useful in the case of a flat selection. So we might as well make it return all the data, flattened into a single array. That would make selection.nodes more useful, and symmetric, as well.

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.