Skip to content

Commit 11faf9a

Browse files
committed
Sunburst Labels and filterRenderedNodes
1 parent fff8eef commit 11faf9a

File tree

7 files changed

+154
-31
lines changed

7 files changed

+154
-31
lines changed

src/components/NetworkFrame.js

Lines changed: 67 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,8 @@ import {
4040
circleNodeGenerator,
4141
areaLink,
4242
ribbonLink,
43-
circularAreaLink
43+
circularAreaLink,
44+
radialLabelGenerator
4445
} from "./svg/networkDrawing"
4546

4647
import { stringToFn } from "./data/dataFunctions"
@@ -190,11 +191,11 @@ function determineNodeIcon(baseCustomNodeIcon, networkSettings, size) {
190191
return sankeyNodeGenerator
191192
case "partition":
192193
return networkSettings.projection === "radial"
193-
? radialRectNodeGenerator(size, center)
194+
? radialRectNodeGenerator(size, center, networkSettings)
194195
: hierarchicalRectNodeGenerator
195196
case "treemap":
196197
return networkSettings.projection === "radial"
197-
? radialRectNodeGenerator(size, center)
198+
? radialRectNodeGenerator(size, center, networkSettings)
198199
: hierarchicalRectNodeGenerator
199200
case "circlepack":
200201
return circleNodeGenerator
@@ -450,7 +451,8 @@ type Props = {
450451
onNodeOut?: Function,
451452
onNodeClick?: Function,
452453
onNodeEnter?: Function,
453-
renderOrder?: $ReadOnlyArray<"edges" | "nodes">
454+
renderOrder?: $ReadOnlyArray<"edges" | "nodes">,
455+
filterRenderedNodes: Function
454456
}
455457

456458
class NetworkFrame extends React.Component<Props, State> {
@@ -461,7 +463,8 @@ class NetworkFrame extends React.Component<Props, State> {
461463
size: [500, 500],
462464
className: "",
463465
name: "networkframe",
464-
networkType: { type: "force", iterations: 500 }
466+
networkType: { type: "force", iterations: 500 },
467+
filterRenderedNodes: () => true
465468
}
466469

467470
static displayName = "NetworkFrame"
@@ -579,7 +582,8 @@ class NetworkFrame extends React.Component<Props, State> {
579582
margin: baseMargin,
580583
hoverAnnotation,
581584
customNodeIcon: baseCustomNodeIcon,
582-
customEdgeIcon: baseCustomEdgeIcon
585+
customEdgeIcon: baseCustomEdgeIcon,
586+
filterRenderedNodes
583587
} = currentProps
584588

585589
let { edgeType } = currentProps
@@ -865,11 +869,14 @@ class NetworkFrame extends React.Component<Props, State> {
865869
radialProjectable[networkSettings.type] &&
866870
networkSettings.projection === "radial"
867871
) {
868-
const radialPoint = pointOnArcAtAngle(
869-
[adjustedSize[0] / 2, adjustedSize[1] / 2],
870-
node.x / adjustedSize[0],
871-
node.y / 2
872-
)
872+
const radialPoint =
873+
node.depth === 0
874+
? [adjustedSize[0] / 2, adjustedSize[1] / 2]
875+
: pointOnArcAtAngle(
876+
[adjustedSize[0] / 2, adjustedSize[1] / 2],
877+
node.x / adjustedSize[0],
878+
node.y / 2
879+
)
873880
node.x = radialPoint[0]
874881
node.y = radialPoint[1]
875882
} else {
@@ -1285,6 +1292,14 @@ class NetworkFrame extends React.Component<Props, State> {
12851292
this.state.graphSettings.edges = currentProps.edges
12861293
}
12871294

1295+
//filter out user-defined nodes
1296+
projectedNodes = projectedNodes.filter(filterRenderedNodes)
1297+
projectedEdges = projectedEdges.filter(
1298+
d =>
1299+
projectedNodes.indexOf(d.target) !== -1 &&
1300+
projectedNodes.indexOf(d.source) !== -1
1301+
)
1302+
12881303
if (networkSettings.direction === "flip") {
12891304
projectedNodes.forEach(node => {
12901305
// const ox = node.x
@@ -1320,8 +1335,9 @@ class NetworkFrame extends React.Component<Props, State> {
13201335
networkSettings.type !== "wordcloud" &&
13211336
networkSettings.type !== "chord" &&
13221337
networkSettings.type !== "sankey" &&
1323-
(hierarchicalTypeHash[networkSettings.type] === undefined ||
1324-
networkSettings.nodeSize)
1338+
networkSettings.type !== "partition" &&
1339+
networkSettings.type !== "treemap" &&
1340+
networkSettings.type !== "circlepack"
13251341
) {
13261342
const xMin = min(projectedNodes.map(p => p.x - nodeSizeAccessor(p)))
13271343
const xMax = max(projectedNodes.map(p => p.x + nodeSizeAccessor(p)))
@@ -1330,13 +1346,38 @@ class NetworkFrame extends React.Component<Props, State> {
13301346

13311347
const projectionScaleX = scaleLinear()
13321348
.domain([xMin, xMax])
1333-
.range([0, adjustedSize[0]])
1349+
.range([margin.left, adjustedSize[0] - margin.right])
1350+
const projectionScaleY = scaleLinear()
1351+
.domain([yMin, yMax])
1352+
.range([margin.top, adjustedSize[1] - margin.bottom])
1353+
projectedNodes.forEach(node => {
1354+
node.x = projectionScaleX(node.x)
1355+
node.y = projectionScaleY(node.y)
1356+
})
1357+
} else if (
1358+
networkSettings.zoom !== false &&
1359+
networkSettings.projection !== "radial" &&
1360+
(networkSettings.type === "partition" ||
1361+
networkSettings.type === "treemap")
1362+
) {
1363+
const xMin = min(projectedNodes.map(p => p.x0))
1364+
const xMax = max(projectedNodes.map(p => p.x1))
1365+
const yMin = min(projectedNodes.map(p => p.y0))
1366+
const yMax = max(projectedNodes.map(p => p.y1))
1367+
1368+
const projectionScaleX = scaleLinear()
1369+
.domain([xMin, xMax])
1370+
.range([margin.left, adjustedSize[0] - margin.right])
13341371
const projectionScaleY = scaleLinear()
13351372
.domain([yMin, yMax])
1336-
.range([0, adjustedSize[1]])
1373+
.range([margin.top, adjustedSize[1] - margin.bottom])
13371374
projectedNodes.forEach(node => {
13381375
node.x = projectionScaleX(node.x)
13391376
node.y = projectionScaleY(node.y)
1377+
node.x0 = projectionScaleX(node.x0)
1378+
node.y0 = projectionScaleY(node.y0)
1379+
node.x1 = projectionScaleX(node.x1)
1380+
node.y1 = projectionScaleY(node.y1)
13401381
})
13411382
}
13421383

@@ -1408,9 +1449,17 @@ class NetworkFrame extends React.Component<Props, State> {
14081449
projectedNodes.forEach((node, nodei) => {
14091450
if (nodeLabels === true || (nodeLabels && nodeLabels(node, nodei))) {
14101451
const actualLabel =
1411-
nodeLabels === true
1412-
? nodeIDAccessor(node, nodei)
1413-
: nodeLabels(node, nodei)
1452+
networkSettings.projection === "radial" && node.depth !== 0
1453+
? radialLabelGenerator(
1454+
node,
1455+
nodei,
1456+
nodeLabels === true ? nodeIDAccessor : nodeLabels,
1457+
adjustedSize,
1458+
networkSettings
1459+
)
1460+
: nodeLabels === true
1461+
? nodeIDAccessor(node, nodei)
1462+
: nodeLabels(node, nodei)
14141463

14151464
let nodeLabel
14161465

src/components/annotationRules/networkframeRules.js

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -143,14 +143,23 @@ export const svgRectEncloseRule = ({
143143
}
144144

145145
const bboxNodes = selectedNodes.map(p => {
146+
if (p.shapeNode) {
147+
return {
148+
x0: p.x0,
149+
x1: p.x1,
150+
y0: p.y0,
151+
y1: p.y1
152+
}
153+
}
146154
const nodeSize = nodeSizeAccessor(p)
147155
return {
148-
x0: p.x0 === p.shapeNode ? p.x0 : p.x - nodeSize,
149-
x1: p.x1 === p.shapeNode ? p.x1 : p.x + nodeSize,
150-
y0: p.y0 === p.shapeNode ? p.y0 : p.y - nodeSize,
151-
y1: p.y1 === p.shapeNode ? p.y1 : p.y + nodeSize
156+
x0: p.x - nodeSize,
157+
x1: p.x + nodeSize,
158+
y0: p.y - nodeSize,
159+
y1: p.y + nodeSize
152160
}
153161
})
162+
154163
return rectangleEnclosure({ bboxNodes, d, i })
155164
}
156165

src/components/constants/frame_props.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -329,7 +329,8 @@ export const networkframeproptypes = {
329329
edgeStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.func]),
330330
customNodeIcon: PropTypes.func,
331331
zoomToFit: PropTypes.bool,
332-
edgeType: PropTypes.oneOfType([PropTypes.string, PropTypes.func])
332+
edgeType: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
333+
filterRenderedNodes: PropTypes.func
333334
}
334335

335336
export const responsiveprops = {

src/components/svg/networkDrawing.js

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import { arc } from "d3-shape"
1010
import { linearRibbon } from "./SvgHelper"
1111
import { interpolateNumber } from "d3-interpolate"
1212

13+
import { scaleLinear } from "d3-scale"
14+
1315
function sankeyEdgeSort(a, b, direction) {
1416
if (a.circular && !b.circular) return -1
1517
if (b.circular && !a.circular) return 1
@@ -195,8 +197,19 @@ export const chordNodeGenerator = size => ({
195197
/>
196198
)
197199

198-
export const radialRectNodeGenerator = (size, center) => {
200+
export const radialRectNodeGenerator = (size, center, type) => {
199201
const radialArc = arc()
202+
const { angleRange = [0, 360] } = type
203+
const rangePct = angleRange.map(d => d / 360)
204+
const rangeMod = rangePct[1] - rangePct[0]
205+
206+
const adjustedPct =
207+
rangeMod < 1
208+
? scaleLinear()
209+
.domain([0, 1])
210+
.range(rangePct)
211+
: d => d
212+
200213
return ({ d, i, styleFn, renderMode, key, className, baseMarkProps }) => {
201214
radialArc.innerRadius(d.y0 / 2).outerRadius(d.y1 / 2)
202215

@@ -207,8 +220,8 @@ export const radialRectNodeGenerator = (size, center) => {
207220
transform={`translate(${center})`}
208221
markType="path"
209222
d={radialArc({
210-
startAngle: (d.x0 / size[0]) * Math.PI * 2,
211-
endAngle: (d.x1 / size[0]) * Math.PI * 2
223+
startAngle: adjustedPct(d.x0 / size[0]) * Math.PI * 2,
224+
endAngle: adjustedPct(d.x1 / size[0]) * Math.PI * 2
212225
})}
213226
style={styleFn(d, i)}
214227
renderMode={renderMode ? renderMode(d, i) : undefined}
@@ -220,6 +233,24 @@ export const radialRectNodeGenerator = (size, center) => {
220233
}
221234
}
222235

236+
export const radialLabelGenerator = (node, nodei, nodeIDAccessor, size) => {
237+
const anglePct = (node.x1 + node.x0) / 2 / size[0]
238+
const nodeLabel = nodeIDAccessor(node, nodei)
239+
const labelRotate = anglePct > 0.5 ? anglePct * 360 + 90 : anglePct * 360 - 90
240+
241+
return (
242+
<g transform={`rotate(${labelRotate})`}>
243+
{typeof nodeLabel === "string" ? (
244+
<text textAnchor="middle" y={5}>
245+
{nodeLabel}
246+
</text>
247+
) : (
248+
nodeLabel
249+
)}
250+
</g>
251+
)
252+
}
253+
223254
export const hierarchicalRectNodeGenerator = ({
224255
d,
225256
i,

src/docs/components/Dendrogram.js

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,10 @@ export default class Dendrogram extends React.Component {
1717
super(props)
1818

1919
this.state = {
20-
type: "circlepack",
20+
type: "treemap",
2121
projection: "vertical",
22-
annotation: "rectangle"
22+
annotation: "rectangle",
23+
filter: "none"
2324
}
2425
}
2526
render() {
@@ -48,6 +49,12 @@ export default class Dendrogram extends React.Component {
4849
</MenuItem>
4950
))
5051

52+
const filterOptions = ["none", "time", "layout", "geo"].map(d => (
53+
<MenuItem key={`type-option-${d}`} label={d} value={d}>
54+
{d}
55+
</MenuItem>
56+
))
57+
5158
const buttons = [
5259
<FormControl key="button-1-0-0">
5360
<InputLabel htmlFor="chart-type-input">Chart Type</InputLabel>
@@ -75,6 +82,15 @@ export default class Dendrogram extends React.Component {
7582
>
7683
{annotationOptions}
7784
</Select>
85+
</FormControl>,
86+
<FormControl key="button-4-0-0">
87+
<InputLabel htmlFor="chart-projection-input">Filter</InputLabel>
88+
<Select
89+
value={this.state.filter}
90+
onChange={e => this.setState({ filter: e.target.value })}
91+
>
92+
{filterOptions}
93+
</Select>
7894
</FormControl>
7995
]
8096

@@ -85,7 +101,8 @@ export default class Dendrogram extends React.Component {
85101
demo: DendrogramRaw({
86102
annotation: this.state.annotation,
87103
type: this.state.type,
88-
projection: this.state.projection
104+
projection: this.state.projection,
105+
filter: this.state.filter
89106
}),
90107
source: `const annotations = [
91108
{

src/docs/components/DendrogramRaw.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,12 @@ const colors = ["#00a2ce", "#b6a756", "#4d430c", "#b3331d"]
88
export default ({
99
annotation = "rectangle",
1010
type = "dendrogram",
11-
projection
11+
projection,
12+
filter
1213
}) => {
14+
const nodeFilter =
15+
filter === "none" ? undefined : d => d.parent && d.parent.name === filter
16+
1317
const hierarchicalChart = {
1418
title: "D3v3 API",
1519
size: [700, 700],
@@ -49,6 +53,7 @@ export default ({
4953
</div>
5054
)
5155
},
56+
filterRenderedNodes: nodeFilter,
5257
annotations: [
5358
{
5459
type: annotation === "rectangle" ? "enclose-rect" : "enclose",

src/docs/components/SunburstRaw.js

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,18 @@ const sunburstSettings = {
3131
<p>{d.data.name}</p>
3232
</div>
3333
),
34-
margin: 10
34+
nodeLabels: d => {
35+
return d.x1 - d.x0 < 8 ? null : (
36+
<g transform="translate(0,5)">
37+
<text textAnchor="middle" strokeWidth={2} stroke="white" fill="white">
38+
{d.id}
39+
</text>
40+
<text textAnchor="middle">{d.id}</text>
41+
</g>
42+
)
43+
},
44+
margin: 10,
45+
filterRenderedNodes: d => d.depth !== 0
3546
}
3647
export default (
3748
<div>

0 commit comments

Comments
 (0)