Skip to content

Commit 8f028a5

Browse files
committed
Implement filters using crossfilter
Issue: #6
1 parent 72d3d64 commit 8f028a5

File tree

4 files changed

+145
-96
lines changed

4 files changed

+145
-96
lines changed

package-lock.json

+13
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"dependencies": {
66
"axios": "^0.18.0",
77
"crossfilter": "^1.3.12",
8+
"crossfilter2": "^1.4.6",
89
"d3": "^5.7.0",
910
"date-fns": "^2.0.0-alpha.16",
1011
"leaflet": "^1.3.4",

src/pages/EnvironmentPage/Filter.js

+119
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
import React, { Component } from "react";
2+
import { scaleLinear, max, min } from "d3";
3+
import {
4+
XYPlot,
5+
VerticalBarSeries,
6+
VerticalGridLines,
7+
HorizontalGridLines,
8+
XAxis,
9+
YAxis,
10+
Highlight
11+
} from "react-vis";
12+
13+
export default class Filter extends Component {
14+
constructor(props) {
15+
super(props);
16+
17+
const { dataset, attribute } = this.props;
18+
19+
const dimension = dataset.dimension(d => Number(d[attribute]));
20+
const allData = dataset.all();
21+
22+
const dataMax = max(allData, d => Number(d[attribute]));
23+
const dataMin = min(allData, d => Number(d[attribute]));
24+
25+
const group = dimension.group();
26+
27+
const scaleX = scaleLinear()
28+
.domain([dataMin - dataMin / 10, dataMax + dataMax / 10])
29+
.rangeRound([0, 275]);
30+
const scaleY = scaleLinear()
31+
.domain([0, group.top(1)[0].value])
32+
.range([0, 100]);
33+
34+
const data = group.all().map(d => ({
35+
x: scaleX(d.key),
36+
y: scaleY(d.value)
37+
}));
38+
39+
this.state = {
40+
selectionStart: null,
41+
selectionEnd: null,
42+
dimension,
43+
data
44+
};
45+
}
46+
47+
handleFilterSelection = area => {
48+
this.setState(
49+
({ dimension: prevDimension }) => {
50+
const selectionStart = area ? area.left : null;
51+
const selectionEnd = area ? area.right : null;
52+
53+
if (selectionStart === null || selectionEnd === null) {
54+
return {
55+
selectionStart,
56+
selectionEnd,
57+
dimension: prevDimension.filterAll()
58+
};
59+
}
60+
61+
return {
62+
selectionStart,
63+
selectionEnd,
64+
dimension: prevDimension.filter([selectionStart, selectionEnd])
65+
};
66+
},
67+
() => {
68+
console.log(this.state.dimension.top(Infinity));
69+
}
70+
);
71+
};
72+
73+
componentWillUnmount = () => {
74+
this.state.dimension.dispose();
75+
};
76+
77+
render = () => {
78+
const { attribute } = this.props;
79+
const { selectionStart, selectionEnd, data } = this.state;
80+
81+
return (
82+
<div>
83+
<h6>
84+
<code>{attribute}</code>
85+
</h6>
86+
87+
<XYPlot height={300} width={300}>
88+
<VerticalGridLines />
89+
<HorizontalGridLines />
90+
<XAxis />
91+
<YAxis />
92+
<VerticalBarSeries
93+
data={data}
94+
colorType="literal"
95+
getColor={d => {
96+
if (selectionStart === null || selectionEnd === null) {
97+
return "#1E96BE";
98+
}
99+
100+
const inX = d.x >= selectionStart && d.x <= selectionEnd;
101+
const inX0 = d.x0 >= selectionStart && d.x0 <= selectionEnd;
102+
const inStart = selectionStart >= d.x0 && selectionStart <= d.x;
103+
const inEnd = selectionEnd >= d.x0 && selectionEnd <= d.x;
104+
105+
return inStart || inEnd || inX || inX0 ? "#12939A" : "#1E96BE";
106+
}}
107+
/>
108+
<Highlight
109+
color="#829AE3"
110+
drag
111+
enableY={false}
112+
onDrag={this.handleFilterSelection}
113+
onDragEnd={this.handleFilterSelection}
114+
/>
115+
</XYPlot>
116+
</div>
117+
);
118+
};
119+
}

src/pages/EnvironmentPage/index.js

+12-96
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,14 @@
11
import React, { Component } from "react";
22
import styled from "styled-components";
33
import { csv, median, max } from "d3";
4-
import {
5-
XYPlot,
6-
VerticalBarSeries,
7-
VerticalGridLines,
8-
HorizontalGridLines,
9-
XAxis,
10-
YAxis,
11-
Highlight
12-
} from "react-vis";
4+
import * as crossfilter from "crossfilter2";
135

146
import * as api from "../../api";
157
import Map from "../../components/Map";
168
import { MenuControl } from "../../components/Controls";
179

10+
import Filter from "./Filter";
11+
1812
import "react-vis/dist/style.css";
1913

2014
const normalFillColor = "#2196F3";
@@ -44,18 +38,6 @@ export default class EnvironmentPage extends Component {
4438
filters: {}
4539
};
4640

47-
handleFilterSelection = key => area => {
48-
this.setState(({ filters }) => ({
49-
filters: {
50-
...filters,
51-
[key]: {
52-
selectionStart: area && area.left,
53-
selectionEnd: area && area.right
54-
}
55-
}
56-
}));
57-
};
58-
5941
handleChange = key => event => {
6042
const { dataset } = this.state;
6143
const { value } = event.target;
@@ -119,85 +101,13 @@ export default class EnvironmentPage extends Component {
119101
lat: median(dataset, d => d[data.latitudeAttr]),
120102
lng: median(dataset, d => d[data.longitudeAttr])
121103
},
122-
dataset
104+
dataset: crossfilter(dataset)
123105
});
124106
}
125107
);
126108
});
127109
}
128110

129-
renderFilter({ description: attribute }) {
130-
const { dataset, filters } = this.state;
131-
132-
const aggregation = dataset.reduce((aggr, curr) => {
133-
const x = Number(curr[attribute]);
134-
135-
if (Number.isNaN(x)) {
136-
return aggr;
137-
}
138-
139-
return {
140-
...aggr,
141-
[x]: 1 + (aggr[x] || 0)
142-
};
143-
}, {});
144-
145-
const data = Object.keys(aggregation)
146-
.map(key => {
147-
const x = Number(key);
148-
149-
return {
150-
x,
151-
y: aggregation[x]
152-
};
153-
})
154-
.filter(Boolean);
155-
156-
return (
157-
<div key={attribute}>
158-
<h6>
159-
<code>{attribute}</code>
160-
</h6>
161-
162-
<XYPlot height={300} width={300}>
163-
<VerticalGridLines />
164-
<HorizontalGridLines />
165-
<XAxis />
166-
<YAxis />
167-
<VerticalBarSeries
168-
data={data}
169-
colorType="literal"
170-
getColor={d => {
171-
if (!filters[attribute]) {
172-
return "#1E96BE";
173-
}
174-
175-
const { selectionStart, selectionEnd } = filters[attribute];
176-
177-
if (selectionStart === null || selectionEnd === null) {
178-
return "#1E96BE";
179-
}
180-
181-
const inX = d.x >= selectionStart && d.x <= selectionEnd;
182-
const inX0 = d.x0 >= selectionStart && d.x0 <= selectionEnd;
183-
const inStart = selectionStart >= d.x0 && selectionStart <= d.x;
184-
const inEnd = selectionEnd >= d.x0 && selectionEnd <= d.x;
185-
186-
return inStart || inEnd || inX || inX0 ? "#12939A" : "#1E96BE";
187-
}}
188-
/>
189-
<Highlight
190-
color="#829AE3"
191-
drag
192-
enableY={false}
193-
onDrag={this.handleFilterSelection(attribute)}
194-
onDragEnd={this.handleFilterSelection(attribute)}
195-
/>
196-
</XYPlot>
197-
</div>
198-
);
199-
}
200-
201111
render() {
202112
const {
203113
loading,
@@ -217,7 +127,13 @@ export default class EnvironmentPage extends Component {
217127
return (
218128
<Wrapper>
219129
<FilterContainer>
220-
{numericAttributes.map(attr => this.renderFilter(attr))}
130+
{numericAttributes.slice(0, 2).map(attr => (
131+
<Filter
132+
key={attr.description}
133+
dataset={dataset}
134+
attribute={attr.description}
135+
/>
136+
))}
221137
</FilterContainer>
222138
<MapContainer>
223139
<MenuControl
@@ -232,7 +148,7 @@ export default class EnvironmentPage extends Component {
232148
/> */}
233149
<Map
234150
center={center}
235-
dataset={dataset}
151+
dataset={dataset.allFiltered()}
236152
getColor={this.getColor}
237153
getSize={this.getSize}
238154
latitudeSelector={p => p[meta.latitudeAttr]}

0 commit comments

Comments
 (0)