Skip to content

Commit 740a0e5

Browse files
committed
Enable transition
1 parent b232ff8 commit 740a0e5

File tree

5 files changed

+252
-29
lines changed

5 files changed

+252
-29
lines changed
File renamed without changes.

src/data.mini.b.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
const DATA = {
2+
name: 'ROOT',
3+
children: [
4+
{
5+
name: 'A',
6+
size: 10
7+
},
8+
{
9+
name: 'B',
10+
size: 10
11+
},
12+
{
13+
name: 'C',
14+
children: [
15+
{
16+
name: '1',
17+
size: 12
18+
},
19+
{
20+
name: '2',
21+
size: 8
22+
},
23+
]
24+
}
25+
]
26+
};
27+
export default DATA;

src/data.mini.c.js

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
const DATA = {
2+
name: 'ROOT',
3+
children: [
4+
{
5+
name: 'A',
6+
size: 10
7+
},
8+
{
9+
name: 'B',
10+
children: [
11+
{
12+
name: '1',
13+
size: 4
14+
},
15+
{
16+
name: '2',
17+
children: [
18+
{
19+
name: 'V',
20+
size: 2
21+
},
22+
{
23+
name: 'W',
24+
size: 3
25+
},
26+
{
27+
name: 'X',
28+
size: 4
29+
},
30+
{
31+
name: 'Y',
32+
size: 6
33+
},
34+
{
35+
name: 'Z',
36+
size: 8
37+
},
38+
]
39+
},
40+
{
41+
name: '3',
42+
size: 6
43+
},
44+
]
45+
},
46+
{
47+
name: 'C',
48+
size: 20
49+
},
50+
{
51+
name: 'D',
52+
size: 25
53+
},
54+
{
55+
name: 'E',
56+
size: 40
57+
},
58+
{
59+
name: 'F',
60+
size: 10
61+
},
62+
]
63+
};
64+
export default DATA;

src/home.js

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { Tooltip, actions as tooltipActions } from 'redux-tooltip';
55
import copy from 'deepcopy';
66
import Treemap from './treemap';
77
import { moveDown } from './actions';
8-
import DATA from './data.mini';
8+
import DATA from './data';
99

1010
function collapse(path) {
1111
let collapsed = copy(DATA), focus = collapsed;
@@ -39,6 +39,29 @@ function collapse(path) {
3939
return collapsed;
4040
}
4141

42+
function crop(path) {
43+
let cropped = DATA;
44+
const remain = [...path];
45+
while (0 < remain.length) {
46+
const name = remain.shift();
47+
if (typeof cropped.children === 'undefined') {
48+
console.warn(`No children, this is a leaf node`);
49+
break;
50+
}
51+
const matches = cropped.children.filter(c => c.name === name);
52+
if (1 < matches.length) {
53+
console.warn(`Found multiple nodes which have same name: '${name}'`);
54+
break;
55+
}
56+
if (matches.length === 0) {
57+
console.warn(`No child named '${name}'`);
58+
break;
59+
}
60+
cropped = matches[0];
61+
}
62+
return copy(cropped);
63+
}
64+
4265
@connect(({ app }) => ({ app }))
4366
export default class Home extends Component {
4467
static displayName = 'Home';
@@ -69,7 +92,7 @@ export default class Home extends Component {
6992
render() {
7093
const { location } = this.props;
7194
const path = location.pathname.split('/').filter(s => 0 < s.length);
72-
const data = collapse(path);
95+
const data = crop(path);
7396
return (
7497
<div id="home" style={{ position: 'relative' }}>
7598
<h1>treemap-with-router</h1>
@@ -83,7 +106,7 @@ export default class Home extends Component {
83106
))}
84107
</h2>
85108
<Treemap
86-
data={data}
109+
{ ...{ data, path } }
87110
onMoveDown={::this.handleMoveDown}
88111
onShowDetail={::this.handleHover}
89112
onHideDetail={::this.handleLeave}

src/treemap.js

Lines changed: 135 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,76 +1,185 @@
11
import React, { Component, PropTypes } from 'react';
22
import d3 from 'd3';
3+
import copy from 'deepcopy';
34
import equal from 'deep-equal';
45

56
const WIDTH = 800;
67
const HEIGHT = 400;
8+
const DURATION = 750;
79
const color = d3.scale.category10();
810

911
export default class Treemap extends Component {
1012
static displayName = 'Treemap';
1113
static propTypes = {
1214
data: PropTypes.object.isRequired,
15+
path: PropTypes.array.isRequired,
1316
onMoveDown: PropTypes.func.isRequired,
1417
onShowDetail: PropTypes.func.isRequired,
1518
onHideDetail: PropTypes.func.isRequired,
1619
};
1720

21+
constructor(props) {
22+
super(props);
23+
24+
this.treemap = d3.layout.treemap()
25+
.children(d => d.children)
26+
.value(d => d.size)
27+
.size([WIDTH, HEIGHT]);
28+
29+
this.state = { prev: {} };
30+
}
31+
1832
componentDidMount() {
1933
this.renderTreemap();
2034
}
2135

2236
componentDidUpdate(prevProps) {
23-
if (!equal(prevProps.data, this.props.data)) {
24-
this.renderTreemap();
37+
if (!equal(prevProps.path, this.props.path)) {
38+
this.setState({
39+
prev: {
40+
data: copy(prevProps.data),
41+
path: copy(prevProps.path),
42+
}
43+
}, () => {
44+
this.renderTreemap();
45+
});
2546
}
2647
}
2748

2849
renderTreemap() {
29-
if (this.svg) {
30-
d3.select(this.refs.content).selectAll('svg').remove();
50+
const { data, path, onMoveDown, onShowDetail, onHideDetail } = this.props;
51+
const { prev: { data: prevData, path: prevPath } } = this.state;
52+
53+
if (!this.svg) {
54+
this.svg = d3.select(this.refs.content)
55+
.append('svg')
56+
.attr('width', WIDTH)
57+
.attr('height', HEIGHT);
3158
}
3259

33-
this.svg = d3.select(this.refs.content).append('svg')
34-
.attr('width', WIDTH)
35-
.attr('height', HEIGHT);
60+
// Populate for current data
61+
const currentNodes = this.treemap
62+
.nodes(copy(data)).filter(d => d.depth === 1);
3663

37-
const { data, onMoveDown, onShowDetail, onHideDetail } = this.props;
64+
// Get original position before populating layout
65+
let x, y, prevNodes;
66+
if (typeof prevData !== 'undefined') {
67+
// Populate for prev data
68+
prevNodes = this.treemap
69+
.nodes(copy(prevData)).filter(d => d.depth === 1);
3870

39-
const layer = this.svg.append('g');
71+
// Check UP or DOWN
72+
if (prevPath.length < path.length) {
73+
let orig = prevNodes.filter(child => child.name === data.name)[0];
74+
// console.log('Down', {...orig});
75+
x = d3.scale.linear()
76+
.domain([orig.x, orig.x + orig.dx])
77+
.range([0, WIDTH]);
78+
y = d3.scale.linear()
79+
.domain([orig.y, orig.y + orig.dy])
80+
.range([0, HEIGHT]);
81+
} else {
82+
let orig = currentNodes.filter(child => child.name === prevData.name)[0];
83+
// console.log('Up', {...orig});
84+
x = d3.scale.linear()
85+
.domain([0, WIDTH])
86+
.range([orig.x, orig.x + orig.dx]);
87+
y = d3.scale.linear()
88+
.domain([0, HEIGHT])
89+
.range([orig.y, orig.y + orig.dy]);
90+
}
91+
}
4092

41-
const treemap = d3.layout.treemap()
42-
.children(d => d.children)
43-
.value(d => d.size)
44-
.size([WIDTH, HEIGHT]);
93+
if (typeof x === 'undefined' && typeof y === 'undefined') {
94+
x = d3.scale.linear()
95+
.domain([0, WIDTH])
96+
.range([0, WIDTH]);
97+
y = d3.scale.linear()
98+
.domain([0, HEIGHT])
99+
.range([0, HEIGHT]);
100+
}
45101

46-
const next = d => d.depth === 1;
47-
const nodes = treemap.nodes(data);
48-
const children = layer.selectAll('g').data(nodes);
102+
// console.log('x, y',
103+
// `${JSON.stringify(x.domain())} => ${JSON.stringify(x.range())}`,
104+
// `${JSON.stringify(y.domain())} => ${JSON.stringify(y.range())}`);
49105

50-
children
106+
const ix = d => x.invert(d);
107+
const iy = d => y.invert(d);
108+
109+
if (typeof prevData !== 'undefined') {
110+
// [1] Layer for Prev Level
111+
const prev = {};
112+
prev.layer = this.svg.select('.current')
113+
.attr('class', 'prev');
114+
115+
// [JOIN] prev
116+
prev.child = prev.layer.selectAll('.child')
117+
.data(prevNodes);
118+
119+
// [UPDATE] prev: child
120+
prev.child
121+
.transition().duration(DURATION)
122+
.attr('transform', d => `translate(${x(d.x)},${y(d.y)})`);
123+
124+
// [UPDATE] prev: child > rect
125+
prev.rect = prev.child.select('rect')
126+
.transition().duration(DURATION)
127+
.attr('width', d => x(d.x + d.dx) - x(d.x))
128+
.attr('height', d => y(d.y + d.dy) - y(d.y));
129+
130+
// Remove prev layer after transition
131+
prev.layer
132+
.transition().duration(DURATION)
133+
.delay(DURATION)
134+
.remove();
135+
}
136+
137+
// [2] Layer for Current Level
138+
const current = {};
139+
current.layer = this.svg
140+
.append('g')
141+
.attr('class', 'current');
142+
143+
// [JOIN] current
144+
current.child = current.layer.selectAll('.child')
145+
.data(currentNodes);
146+
147+
// [ENTER] current: child
148+
current.enter = current.child
51149
.enter().append('g')
52-
.filter(next)
53-
.attr('transform', d => `translate(${d.x},${d.y})`)
150+
.attr('class', 'child')
151+
.attr('transform', d => `translate(${ix(d.x)},${iy(d.y)})`)
54152
.style('cursor', d => d.children ? 'pointer' : 'normal')
55-
.on('click', function (d) {
153+
.style('opacity', 0)
154+
.on('click', d => {
56155
if (d.children) {
57156
onMoveDown(d.name);
58157
}
59158
})
60-
.on('mousemove', function (d) {
159+
.on('mousemove', d => {
61160
const el = document.getElementById('container');
62161
const [x, y] = d3.mouse(el);
63162
onShowDetail({ x, y }, d.name);
64163
})
65-
.on('mouseleave', function (d) {
164+
.on('mouseleave', d => {
66165
onHideDetail();
67166
});
68167

69-
children.append('rect')
70-
.filter(next)
168+
// [ENTER] current: child (transition)
169+
current.child
170+
.transition().duration(DURATION)
171+
.attr('transform', d => `translate(${d.x},${d.y})`)
172+
.style('opacity', 1);
173+
174+
// [ENTER] current: child > rect
175+
current.enter
176+
.append('rect')
177+
.attr('width', d => ix(d.x + d.dx) - ix(d.x))
178+
.attr('height', d => iy(d.y + d.dy) - iy(d.y))
179+
.attr('fill', (d, i) => color(i))
180+
.transition().duration(DURATION)
71181
.attr('width', d => d.dx)
72-
.attr('height', d => d.dy)
73-
.attr('fill', (d, i) => color(i));
182+
.attr('height', d => d.dy);
74183
}
75184

76185
render() {

0 commit comments

Comments
 (0)