-
Notifications
You must be signed in to change notification settings - Fork 1.8k
/
Copy pathgeo-choropleth-chart.js
279 lines (245 loc) · 9.67 KB
/
geo-choropleth-chart.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
/**
* The geo choropleth chart is designed as an easy way to create a crossfilter driven choropleth map
* from GeoJson data. This chart implementation was inspired by
* {@link http://bl.ocks.org/4060606 the great d3 choropleth example}.
*
* Examples:
* - {@link http://dc-js.github.com/dc.js/vc/index.html US Venture Capital Landscape 2011}
* @class geoChoroplethChart
* @memberof dc
* @mixes dc.colorMixin
* @mixes dc.baseMixin
* @example
* // create a choropleth chart under '#us-chart' element using the default global chart group
* var chart1 = dc.geoChoroplethChart('#us-chart');
* // create a choropleth chart under '#us-chart2' element using chart group A
* var chart2 = dc.compositeChart('#us-chart2', 'chartGroupA');
* @param {String|node|d3.selection} parent - Any valid
* {@link https://github.com/mbostock/d3/wiki/Selections#selecting-elements d3 single selector} specifying
* a dom block element such as a div; or a dom element or d3 selection.
* @param {String} [chartGroup] - The name of the chart group this chart instance should be placed in.
* Interaction with a chart will only trigger events and redraws within the chart's group.
* @return {dc.geoChoroplethChart}
*/
dc.geoChoroplethChart = function (parent, chartGroup) {
var _chart = dc.colorMixin(dc.baseMixin({}));
_chart.colorAccessor(function (d) {
return d || 0;
});
var _geoPath = d3.geo.path();
var _projectionFlag;
var _geoJsons = [];
_chart._doRender = function () {
_chart.resetSvg();
for (var layerIndex = 0; layerIndex < _geoJsons.length; ++layerIndex) {
var states = _chart.svg().append('g')
.attr('class', 'layer' + layerIndex);
var regionG = states.selectAll('g.' + geoJson(layerIndex).name)
.data(geoJson(layerIndex).data)
.enter()
.append('g')
.attr('class', geoJson(layerIndex).name);
regionG
.append('path')
.attr('fill', 'white')
.attr('d', _geoPath);
regionG.append('title');
plotData(layerIndex);
}
_projectionFlag = false;
};
function plotData (layerIndex) {
var data = generateLayeredData();
if (isDataLayer(layerIndex)) {
var regionG = renderRegionG(layerIndex);
renderPaths(regionG, layerIndex, data);
renderTitle(regionG, layerIndex, data);
}
}
function generateLayeredData () {
var data = {};
var groupAll = _chart.data();
for (var i = 0; i < groupAll.length; ++i) {
data[_chart.keyAccessor()(groupAll[i])] = _chart.valueAccessor()(groupAll[i]);
}
return data;
}
function isDataLayer (layerIndex) {
return geoJson(layerIndex).keyAccessor;
}
function renderRegionG (layerIndex) {
var regionG = _chart.svg()
.selectAll(layerSelector(layerIndex))
.classed('selected', function (d) {
return isSelected(layerIndex, d);
})
.classed('deselected', function (d) {
return isDeselected(layerIndex, d);
})
.attr('class', function (d) {
var layerNameClass = geoJson(layerIndex).name;
var regionClass = dc.utils.nameToId(geoJson(layerIndex).keyAccessor(d));
var baseClasses = layerNameClass + ' ' + regionClass;
if (isSelected(layerIndex, d)) {
baseClasses += ' selected';
}
if (isDeselected(layerIndex, d)) {
baseClasses += ' deselected';
}
return baseClasses;
});
return regionG;
}
function layerSelector (layerIndex) {
return 'g.layer' + layerIndex + ' g.' + geoJson(layerIndex).name;
}
function isSelected (layerIndex, d) {
return _chart.hasFilter() && _chart.hasFilter(getKey(layerIndex, d));
}
function isDeselected (layerIndex, d) {
return _chart.hasFilter() && !_chart.hasFilter(getKey(layerIndex, d));
}
function getKey (layerIndex, d) {
return geoJson(layerIndex).keyAccessor(d);
}
function geoJson (index) {
return _geoJsons[index];
}
function renderPaths (regionG, layerIndex, data) {
var paths = regionG
.select('path')
.attr('fill', function () {
var currentFill = d3.select(this).attr('fill');
if (currentFill) {
return currentFill;
}
return 'none';
})
.on('click', function (d) {
return _chart.onClick(d, layerIndex);
});
dc.transition(paths, _chart.transitionDuration(), _chart.transitionDelay()).attr('fill', function (d, i) {
return _chart.getColor(data[geoJson(layerIndex).keyAccessor(d)], i);
});
}
_chart.onClick = function (d, layerIndex) {
var selectedRegion = geoJson(layerIndex).keyAccessor(d);
dc.events.trigger(function () {
_chart.filter(selectedRegion);
_chart.redrawGroup();
});
};
function renderTitle (regionG, layerIndex, data) {
if (_chart.renderTitle()) {
regionG.selectAll('title').text(function (d) {
var key = getKey(layerIndex, d);
var value = data[key];
return _chart.title()({key: key, value: value});
});
}
}
_chart._doRedraw = function () {
for (var layerIndex = 0; layerIndex < _geoJsons.length; ++layerIndex) {
plotData(layerIndex);
if (_projectionFlag) {
_chart.svg().selectAll('g.' + geoJson(layerIndex).name + ' path').attr('d', _geoPath);
}
}
_projectionFlag = false;
};
/**
* **mandatory**
*
* Use this function to insert a new GeoJson map layer. This function can be invoked multiple times
* if you have multiple GeoJson data layers to render on top of each other. If you overlay multiple
* layers with the same name the new overlay will override the existing one.
* @method overlayGeoJson
* @memberof dc.geoChoroplethChart
* @instance
* @see {@link http://geojson.org/ GeoJSON}
* @see {@link https://github.com/mbostock/topojson/wiki TopoJSON}
* @see {@link https://github.com/mbostock/topojson/wiki/API-Reference#feature topojson.feature}
* @example
* // insert a layer for rendering US states
* chart.overlayGeoJson(statesJson.features, 'state', function(d) {
* return d.properties.name;
* });
* @param {geoJson} json - a geojson feed
* @param {String} name - name of the layer
* @param {Function} keyAccessor - accessor function used to extract 'key' from the GeoJson data. The key extracted by
* this function should match the keys returned by the crossfilter groups.
* @return {dc.geoChoroplethChart}
*/
_chart.overlayGeoJson = function (json, name, keyAccessor) {
for (var i = 0; i < _geoJsons.length; ++i) {
if (_geoJsons[i].name === name) {
_geoJsons[i].data = json;
_geoJsons[i].keyAccessor = keyAccessor;
return _chart;
}
}
_geoJsons.push({name: name, data: json, keyAccessor: keyAccessor});
return _chart;
};
/**
* Set custom geo projection function. See the available [d3 geo projection
* functions](https://github.com/mbostock/d3/wiki/Geo-Projections).
* @method projection
* @memberof dc.geoChoroplethChart
* @instance
* @see {@link https://github.com/mbostock/d3/wiki/Geo-Projections d3.geo.projection}
* @see {@link https://github.com/d3/d3-geo-projection Extended d3.geo.projection}
* @param {d3.projection} [projection=d3.geo.albersUsa()]
* @return {dc.geoChoroplethChart}
*/
_chart.projection = function (projection) {
_geoPath.projection(projection);
_projectionFlag = true;
return _chart;
};
/**
* Returns all GeoJson layers currently registered with this chart. The returned array is a
* reference to this chart's internal data structure, so any modification to this array will also
* modify this chart's internal registration.
* @method geoJsons
* @memberof dc.geoChoroplethChart
* @instance
* @return {Array<{name:String, data: Object, accessor: Function}>}
*/
_chart.geoJsons = function () {
return _geoJsons;
};
/**
* Returns the {@link https://github.com/mbostock/d3/wiki/Geo-Paths#path d3.geo.path} object used to
* render the projection and features. Can be useful for figuring out the bounding box of the
* feature set and thus a way to calculate scale and translation for the projection.
* @method geoPath
* @memberof dc.geoChoroplethChart
* @instance
* @see {@link https://github.com/mbostock/d3/wiki/Geo-Paths#path d3.geo.path}
* @return {d3.geo.path}
*/
_chart.geoPath = function () {
return _geoPath;
};
/**
* Remove a GeoJson layer from this chart by name
* @method removeGeoJson
* @memberof dc.geoChoroplethChart
* @instance
* @param {String} name
* @return {dc.geoChoroplethChart}
*/
_chart.removeGeoJson = function (name) {
var geoJsons = [];
for (var i = 0; i < _geoJsons.length; ++i) {
var layer = _geoJsons[i];
if (layer.name !== name) {
geoJsons.push(layer);
}
}
_geoJsons = geoJsons;
return _chart;
};
return _chart.anchor(parent, chartGroup);
};