Skip to content
This repository was archived by the owner on Jul 17, 2025. It is now read-only.

Commit acedeba

Browse files
authored
Emmar/instaml 1483 (#327)
* fix hierarical bar zoom and tooltip
1 parent 0d1644c commit acedeba

File tree

1 file changed

+71
-111
lines changed

1 file changed

+71
-111
lines changed

annotation-app/src/libs/hierarchicalChart.js

Lines changed: 71 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -19,41 +19,38 @@ function hierarchicalChart(options) {
1919
.select(container)
2020
.append('svg')
2121
.attr('width', options.width)
22-
.attr('height', height + margin.top + margin.bottom);
23-
24-
x = d3.scaleLinear().range([39, height - margin.top]);
22+
.attr('height', height + margin.top + margin.bottom).call(zoom);;
2523

2624
root = d3
2725
.hierarchy(options.data)
2826
.sum((d) => d.annotated)
2927
.sort((a, b) => b.value - a.value)
3028
.eachAfter((d) => (d.index = d.parent ? (d.parent.index = d.parent.index + 1 || 0) : 0));
3129

32-
x.domain([0, root.value]);
30+
var y = d3
31+
.scaleLinear()
32+
.domain([0, d3.max(root.children, (d) => d.value)])
33+
.nice()
34+
.range([height - margin.bottom, margin.top]);
3335

34-
xAxis = (g) =>
35-
g
36-
.attr('class', 'x-axis')
37-
.attr('transform', `translate(40, 340) rotate(-90)`)
38-
.call(d3.axisTop(x).ticks(height / 80))
39-
.call((g) => (g.selection ? g.selection() : g).append('line').select('.domain').remove())
40-
.selectAll('text')
41-
.attr('transform', `rotate(90)`)
42-
.attr('x', -20)
43-
.attr('y', 0)
44-
.attr('dy', '.35em');
36+
var yAxis = (g) => g
37+
.attr('transform', `translate(${margin.left},0)`)
38+
.call(d3.axisLeft(y))
39+
.call((g) => g.select('.domain'));
40+
41+
var x = d3
42+
.scaleBand()
43+
.domain(root.children.map((d) => d.data.name))
44+
.range([margin.left, width - margin.right])
45+
.padding(0.4);
4546

46-
yAxis = (g) =>
47+
var xAxis = (g) =>
4748
g
48-
.attr('class', 'y-axis')
49-
.attr('transform', `translate(0,300) rotate(-90)`)
50-
.call((g) =>
51-
g
52-
.append('line')
53-
.attr('stroke', 'currentColor')
54-
.attr('y1', margin.left)
55-
.attr('y2', width - margin.right),
56-
);
49+
.attr('transform', `translate(0,${height - margin.bottom})`)
50+
.call(d3.axisBottom(x))
51+
.selectAll('text')
52+
.style('text-anchor', 'start')
53+
.attr('transform', 'rotate(20 -10 10)');
5754

5855
svg
5956
.append('rect')
@@ -69,9 +66,9 @@ function hierarchicalChart(options) {
6966

7067
duration = 0;
7168

72-
svg.append('g').call(yAxis);
69+
svg.append('g').attr('class', 'y-axis').call(yAxis);
7370

74-
svg.append('g').call(xAxis);
71+
svg.append('g').attr('class', 'x-axis').call(xAxis);
7572

7673
down(svg, root);
7774

@@ -83,62 +80,41 @@ function hierarchicalChart(options) {
8380
// Rebind the current node to the background.
8481
svg.select('.background').datum(d);
8582

86-
// Define two sequenced transitions.
87-
const transition1 = svg.transition().duration(duration);
88-
const transition2 = transition1.transition();
89-
9083
// Mark any currently-displayed bars as exiting.
9184
const exit = svg.selectAll('.enter').attr('class', 'exit');
9285

9386
// Entering nodes immediately obscure the clicked-on bar, so hide it.
9487
exit.selectAll('rect').attr('fill-opacity', (p) => (p === d ? 0 : null));
9588

9689
// Transition exiting bars to fade out.
97-
exit.transition(transition1).attr('fill-opacity', 0).remove();
98-
99-
// Enter the new bars for the clicked-on data.
100-
// Per above, entering bars are immediately visible.
101-
const enter = bar(svg, down, d, '.y-axis').attr('fill-opacity', 0);
102-
103-
// Have the text fade-in, even though the bars are visible.
104-
enter.transition(transition1).attr('fill-opacity', 1);
105-
106-
// Transition entering bars to their new y-position.
107-
enter
108-
.selectAll('g')
109-
.attr('transform', stack(d.index))
110-
.transition(transition1)
111-
.attr('transform', stagger());
90+
exit.attr('fill-opacity', 0).remove();
11291

11392
// Update the x-scale domain.
114-
x.domain([0, d3.max(d.children, (d) => d.value)]);
93+
y.domain([0, d3.max(d.children, (d) => d.value)]);
11594

11695
// Update the x-axis.
117-
svg.selectAll('.x-axis').transition(transition2).call(xAxis);
96+
svg.selectAll('.y-axis').call(yAxis);
11897

119-
// Transition entering bars to the new x-scale.
120-
enter
121-
.selectAll('g')
122-
.transition(transition2)
123-
.attr('transform', (d, i) => `translate(0,${barStep * i})`);
98+
// Enter the new bars for the clicked-on data.
99+
// Per above, entering bars are immediately visible.
100+
const enter = bar(svg, down, d, '.x-axis').attr('fill-opacity', 0);
101+
102+
// Have the text fade-in, even though the bars are visible.
103+
enter.attr('fill-opacity', 1);
124104

125105
// Color the bars as parents; they will fade to children if appropriate.
126106
enter
127107
.selectAll('rect')
128108
.attr('fill', color(true))
129109
.attr('fill-opacity', 1)
130-
.transition(transition2)
131110
.attr('fill', (d) => color(!!d.children))
132-
.attr('width', (d) => x(d.value) - x(0));
111+
.attr('height', (d) => y(0) - y(d.value));
133112
}
134113

135114
function bar(svg, down, d, selector) {
136-
barStep = (width - margin.left - margin.right) / d.children.length;
137-
barPadding = 0.4;
138115
const g = svg
139116
.insert('g', selector)
140117
.attr('class', 'enter')
141-
.attr('transform', `translate(60,340) rotate(-90)`)
142118
.attr('text-anchor', 'end')
143119
.style('font', '10px sans-serif');
144120

@@ -149,20 +125,20 @@ function hierarchicalChart(options) {
149125
.attr('cursor', (d) => (!d.children ? null : 'pointer'))
150126
.on('click', (event, d) => (d.value ? down(svg, d) : null));
151127

152-
bar
153-
.append('text')
154-
.attr('x', barStep / 8)
155-
.attr('y', -barPadding - 15)
156-
.attr('dy', '.35em')
157-
.text((d) => d.data.name)
158-
.style('text-anchor', 'start')
159-
.attr('transform', 'rotate(120 10 10)');
128+
x = d3
129+
.scaleBand()
130+
.domain(d.children.map((d) => d.data.name))
131+
.range([margin.left, width - margin.right])
132+
.padding(0.4);
160133

161-
bar
134+
svg.selectAll('.x-axis').call(xAxis);
135+
136+
bar.attr('class', 'bars')
162137
.append('rect')
163-
.attr('x', x(0))
164-
.attr('width', (d) => x(d.value) - x(0))
165-
.attr('height', barStep * (1 - barPadding) > 0 ? barStep * (1 - barPadding) : 0);
138+
.attr('x', (d) => x(d.data.name) + (x.bandwidth() - d3.min([x.bandwidth(), 150])) / 2)
139+
.attr('y', (d) => y(d.value))
140+
.attr('height', (d) => y(0) - y(d.value))
141+
.attr('width', d3.min([x.bandwidth(), 150]));
166142

167143
hierTip = options.tip;
168144
hierTip
@@ -173,7 +149,7 @@ function hierarchicalChart(options) {
173149
.style('padding', '0px 5px 0px 5px')
174150
.style('border-radius', '4px')
175151
.style('font-size', '10px')
176-
.offset([-155, barStep / 4 - barPadding])
152+
.offset([-10, 0])
177153
.html(function (event, d) {
178154
return '<span>' + d.value + '</span>';
179155
});
@@ -202,74 +178,58 @@ function hierarchicalChart(options) {
202178
// Rebind the current node to the background.
203179
svg.select('.background').datum(d.parent);
204180

205-
// Define two sequenced transitions.
206-
const transition1 = svg.transition().duration(duration);
207-
const transition2 = transition1.transition();
208-
209181
// Mark any currently-displayed bars as exiting.
210182
const exit = svg.selectAll('.enter').attr('class', 'exit');
211183

212184
// Update the x-scale domain.
213-
x.domain([0, d3.max(d.parent.children, (d) => d.value)]);
185+
y.domain([0, d3.max(d.parent.children, (d) => d.value)]);
214186

215187
// Update the x-axis.
216-
svg.selectAll('.x-axis').transition(transition1).call(xAxis);
188+
svg.selectAll('.y-axis').call(yAxis);
217189

218-
// Transition exiting bars to the new x-scale.
219-
exit.selectAll('g').transition(transition1).attr('transform', stagger());
220-
221-
// Transition exiting bars to the parent’s position.
222-
exit.selectAll('g').transition(transition2).attr('transform', stack(d.index));
223190

224191
// Transition exiting rects to the new scale and fade to parent color.
225192
exit
226193
.selectAll('rect')
227-
.transition(transition1)
228-
.attr('width', (d) => x(d.value) - x(0))
194+
.attr('width', (d) => d3.min([x.bandwidth(), 150]))
229195
.attr('fill', color(true));
230196

231197
// Transition exiting text to fade out.
232198
// Remove exiting nodes.
233-
exit.transition(transition2).attr('fill-opacity', 0).remove();
199+
exit.attr('fill-opacity', 0).remove();
234200

235201
// Enter the new bars for the clicked-on data's parent.
236202
const enter = bar(svg, down, d.parent, '.exit').attr('fill-opacity', 0);
237203

238-
enter.selectAll('g').attr('transform', (d, i) => `translate(0,${barStep * i})`);
239-
240-
// Transition entering bars to fade in over the full duration.
241-
enter.transition(transition2).attr('fill-opacity', 1);
204+
enter.attr('fill-opacity', 1);
242205

243-
// Color the bars as appropriate.
244-
// Exiting nodes will obscure the parent bar, so hide it.
245-
// Transition entering rects to the new x-scale.
246-
// When the entering parent rect is done, make it visible!
247206
enter
248207
.selectAll('rect')
249208
.attr('fill', (d) => color(!!d.children))
250-
.attr('fill-opacity', (p) => (p === d ? 0 : null))
251-
.transition(transition2)
252-
.attr('width', (d) => x(d.value) - x(0))
209+
.attr('height', (d) => y(0) - y(d.value))
253210
.on('end', function (p) {
254211
d3.select(this).attr('fill-opacity', 1);
255212
});
256213
}
257214

258-
function stack(i) {
259-
let value = 0;
260-
return (d) => {
261-
const t = `translate(${x(value) - x(0)},${barStep * i})`;
262-
value += d.value;
263-
return t;
264-
};
215+
function zoom(svg) {
216+
const extent = [
217+
[margin.left, margin.top],
218+
[width - margin.right, height - margin.top],
219+
];
220+
221+
svg.call(
222+
d3.zoom().scaleExtent([1, 5]).translateExtent(extent).extent(extent).on('zoom', zoomed),
223+
);
224+
225+
function zoomed(event, d) {
226+
x.range([margin.left, width - margin.right].map((d) => event.transform.applyX(d)));
227+
svg
228+
.selectAll('.bars rect')
229+
.attr('x', (d) => x(d.data.name))
230+
.attr('width', x.bandwidth());
231+
svg.selectAll('.x-axis').call(xAxis);
232+
}
265233
}
266234

267-
function stagger() {
268-
let value = 0;
269-
return (d, i) => {
270-
const t = `translate(${x(value) - x(0)},${barStep * i})`;
271-
value += d.value;
272-
return t;
273-
};
274-
}
275235
}

0 commit comments

Comments
 (0)