misoproject/d3.chart — pull request #62 by jugglinmike (2014-01-06 16:19:17+00:00)
Title: Version 0.2 Roadmap

Hey everyone! I know it might not look it, but @iros and I have been hard at
work designing the next iteration of the library. We wanted to share our plans
with you and solicit feedback--let use know what you think!

## Abstracted Data Attributes

Almost immediately after the release of d3.chart, developers identified an
aspect of reusable visualizations that we missed completely: the shape of the
input data. You can see #22 and #12 for discussion, but it boils down to this:
truely reusable charts shouldn't force users to define the input data in any
particular way.

The solution has been frought with gotchas, but I believe we're at a point
where we can support abstract data attributes. Chart authors would be required
to "reserve" the data that their chart relies on:

```javascript
d3.chart("AwesomeChart", {
dataAttrs: ["time", "space"],
initialize: function() {
// d3 code as normal, with references to `d.time` and `d.space`
}
});
```

...and chart consumers could then (optionally) specify a "mapping" of the
chart's data shape to the shape of their own data:

```javascript
var data = [{ tiempo: 230120, espacio: 930 }];
var miGraphico = d3.select("svg").chart("AwesomeChart", {
dataMapping: {
time: function(d) { return d.tiempo; },
space: function(d) { return d.espacio; }
}
});
```

d3.chart would preform this transformation in real-time to avoid the memory
overhead of copying potentially-large datasets wherever possible. To keep
d3.chart more focused, we're implementing this functionality in a standalone
JavaScript module: [DataMap](https://github.com/jugglinmike/datamap).

## Named Mixins and Data Demuxing

Currently, the `Chart#mixin` method kind of does a lot. It creates a new chart
with the specified options and ties its `draw` function to that of the parent
chart. We'd like to simplify the method to accept a name and a chart. This
means you will only have one canonical method for creating charts (`d3.chart`),
and `Chart#mixin` simply creates associations between two charts' `draw`
methods.

```diff
d3.chart("Demo", {
initialize: function() {
-	this.mixin("ChartName", this.base.append("g"), {
-	someOption: 23
-	});
+	var mixinChart = d3.chart(this.base.append("g"), {
+	someOptions: 23
+	});
+	this.mixin("mixinChart", mixinChart);
}
});
```

"Great," you're thinking, "a more verbose API." I know, I know, but stick with
me--it's worth it!

By separating responsibilities like this, we can make it less confusing to wire
up charts in different ways.

Some charts don't need to be redrawn every time new data arrives (@iros and I
refer to these as "decorator" charts--a chart that defines a tooltip would be
one example). In this case, you would just make a chart, and not "mix it in".

Some charts explicitly *want* to share `draw`; often this is the case when you
are combining two charts to visualize two datasets together. In that case, we
might have a "container" chart that mixes together a bar chart and a line
chart:

```javascript
d3.chart("TwoSets", {
dataAttrs: ["series1", "series2"],
initialize: function() {
var bar = d3.chart("BarChart", this.base.append("g"), {});
var line = d3.chart("LineChart", this.base.append("g"), {});

// This is awkward and unintiutive
bar.transform = function(data) {
return d3.chart("BarChart").prototype.transform(data.series1);
};
line.transform = function(data) {
return d3.chart("LineChart").prototype.transofrm(data.series2);
};

this.mixin("bar", bar);
this.mixin("line", line);
}
});
```

As you're accostumed to, the `TwoSets` chart's `draw` method will invoke the
`draw` method of both `BarChart` and `LineChart`. The problem is that, without
the hacky `transform` trick (which [we're currently using in one of our example
charts](https://github.com/misoproject/d3.chart/blob/d915ec64ca799edc743ee01293e086f308af9a88/examples/scripts/hybrid.js#L7-L14),
unfortunately) both charts will be given the same data. I say, "unfortunately"
because this is not what's desired, normally. With named mixins, we can
concisely "demux" input data to more intuitively support this use case:

```diff
d3.chart("TwoSets", {
dataAttrs: ["series1", "series2"],
initialize: function() {
var bar = d3.chart("BarChart", this.base.append("g"), {});
var line = d3.chart("LineChart", this.base.append("g"), {});

-	// This is awkward and unintiutive
-	bar.transform = function(data) {
-	return d3.chart("BarChart").prototype.transform(data.series1);
-	};
-	bar.transform = function(data) {
-	return d3.chart("LineChart").prototype.transofrm(data.series2);
-	};

this.mixin("bar", bar);
this.mixin("line", line);
+	},
+	demux: function(mixinName, data) {
+	if (mixinName === 'bar') {
+	return data.series1;
+	} else {
+	return data.seris2;
+	}
}
});
```

This lets you mix independent charts together more concisely.

## Committing to Constructors

From the start, we've tried to implement a "d3-like" API to promote more
consistent code style for people accustomed to d3.js. This meant, among other
things, hiding away constructor functions and supporting more declarative forms
for instantiated charts ("look ma, no `new`!).

Unfortunately, this approach makes it difficult to use module systems like AMD
or CommonJS (see #41). All along, you've been able to access chart
constructors, but their usage hasn't been documented or backed by tests. In
version 0.2, we'd like to formally define the behavior of the constructors
created by `d3.chart` and `Chart#extend`. This won't be an API change but a
formal endorsement of an alternative way of handling Charts.

## Simplified Internals

We want to make the internals a little more approachable. To do this, we plan
to remove the `variadicNew` function and instantiate Charts directly with the
`new` operator. This will limit the API a bit, though: you won't be able to
write `initialize` methods with arbitrary arity--all charts will be limited to
a single "options" parameter.

---

So what do you think? It's a lot of change, but we think (given a proper
migration guide), it will be worth it.