Skip to content

Commit d3cfd6e

Browse files
feat(StateVisualizer): Support customization of state node labels
Adds `options.stateVisualizer.node.label` customization api to options object
1 parent f4a53a3 commit d3cfd6e

File tree

5 files changed

+129
-89
lines changed

5 files changed

+129
-89
lines changed

README.md

Lines changed: 77 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,13 @@ Visualizes the state tree and transitions in UI-Router 1.0+.
1010

1111
This script augments your app with two components:
1212

13-
1) State Visualizer: Your UI-Router state tree, showing the active state and its active ancestors (green nodes)
13+
1. State Visualizer: Your UI-Router state tree, showing the active state and its active ancestors (green nodes)
14+
1415
- Clicking a state will transition to that state.
1516
- If your app is large, state trees can be collapsed by double-clicking a state.
16-
- Supports different layouts and zoom.
17+
- Supports different layouts and zoom.
1718

18-
2) Transition Visualizer: A list of each transition (from one state to another)
19+
2. Transition Visualizer: A list of each transition (from one state to another)
1920

2021
- Color coded Transition status (success/error/ignored/redirected)
2122
- Hover over a Transition to show which states were entered/exited, or retained during the transition.
@@ -28,37 +29,37 @@ Register the plugin with the `UIRouter` object.
2829

2930
### Locate the Plugin
3031

31-
- Using a `<script>` tag
32-
33-
Add the script as a tag in your HTML.
34-
35-
```html
36-
<script src="//unpkg.com/@uirouter/visualizer@4"></script>
37-
```
38-
39-
The visualizer Plugin can be found (as a global variable) on the window object.
40-
41-
```js
42-
var Visualizer = window['@uirouter/visualizer'].Visualizer;
43-
```
44-
45-
- Using `require` or `import` (SystemJS, Webpack, etc)
46-
47-
Add the npm package to your project
48-
49-
```
50-
npm install --save @uirouter/visualizer
51-
```
52-
53-
- Use `require` or ES6 `import`:
54-
55-
```js
56-
var Visualizer = require('@uirouter/visualizer').Visualizer;
57-
```
58-
59-
```js
60-
import { Visualizer } from '@uirouter/visualizer';
61-
```
32+
- Using a `<script>` tag
33+
34+
Add the script as a tag in your HTML.
35+
36+
```html
37+
<script src="//unpkg.com/@uirouter/visualizer@4"></script>
38+
```
39+
40+
The visualizer Plugin can be found (as a global variable) on the window object.
41+
42+
```js
43+
var Visualizer = window['@uirouter/visualizer'].Visualizer;
44+
```
45+
46+
- Using `require` or `import` (SystemJS, Webpack, etc)
47+
48+
Add the npm package to your project
49+
50+
```
51+
npm install --save @uirouter/visualizer
52+
```
53+
54+
- Use `require` or ES6 `import`:
55+
56+
```js
57+
var Visualizer = require('@uirouter/visualizer').Visualizer;
58+
```
59+
60+
```js
61+
import { Visualizer } from '@uirouter/visualizer';
62+
```
6263

6364
### Register the plugin
6465

@@ -79,18 +80,52 @@ var pluginInstance = uiRouterInstance.plugin(Visualizer);
7980

8081
### Configuring the plugin
8182

82-
Optionally you can pass configuration to how the visualizer displays the state tree and the transitions.
83+
You can pass a configuration object when registering the plugin.
84+
The configuration object may have the following fields:
85+
86+
- `state`: (boolean) State Visualizer is not rendered when this is `false`
87+
- `transition`: (boolean) Transition Visualizer is not rendered when this is `false`
88+
- `stateVisualizer.node.label`: (function) A function that returns the label for a node
89+
- `stateVisualizer.node.classes`: (function) A function that returns classnames to apply to a node
90+
91+
#### `stateVisualizer.node.label`
92+
93+
The labels for tree nodes can be customized.
94+
95+
Provide a function that accepts the node object and the default label and returns a string:
96+
97+
```
98+
function(node, defaultLabel) { return "label"; }
99+
```
100+
101+
This example adds ` (future)` to future states.
102+
_Note: `node.self` contains a reference to the state declaration object._
103+
104+
```js
105+
var options = {
106+
stateVisualizer: {
107+
node: {
108+
label: function (node, defaultLabel) {
109+
return node.self.name.endsWith('.**') ? defaultLabel + ' (future)' : defaultLabel;
110+
},
111+
},
112+
},
113+
};
83114

84-
The state tree visualizer can be configured to style each node specifically.
115+
var pluginInstance = uiRouterInstance.plugin(Visualizer, options);
116+
```
85117

86-
Example below marks every node with angular.js view with is-ng1 class.
118+
#### `stateVisualizer.node.classes`
119+
120+
The state tree visualizer can be configured to add additional classes to nodes.
121+
Example below marks every node with angular.js view with `is-ng1` class.
87122

88123
```js
89124
var options = {
90125
stateVisualizer: {
91126
node: {
92127
classes(node) {
93-
return Object.entries(node.views || {}).some(routeView => routeView[1] && routeView[1].$type === 'ng1')
128+
return Object.entries(node.views || {}).some((routeView) => routeView[1] && routeView[1].$type === 'ng1')
94129
? 'is-ng1'
95130
: '';
96131
},
@@ -109,8 +144,8 @@ Inject the `$uiRouter` router instance in a run block.
109144

110145
```js
111146
// inject the router instance into a `run` block by name
112-
app.run(function($uiRouter) {
113-
var pluginInstance = $uiRouter.plugin(Visualizer);
147+
app.run(function ($uiRouter) {
148+
var pluginInstance = $uiRouter.plugin(Visualizer);
114149
});
115150
```
116151

@@ -124,7 +159,7 @@ import { Visualizer } from "@uirouter/visualizer";
124159

125160
...
126161

127-
export function configRouter(router: UIRouter) {
162+
export function configRouter(router: UIRouter) {
128163
var pluginInstance = router.plugin(Visualizer);
129164
}
130165

@@ -137,7 +172,6 @@ export function configRouter(router: UIRouter) {
137172
138173
#### React (Imperative)
139174
140-
141175
Create the UI-Router instance manually by calling `new UIRouterReact();`
142176
143177
```js
@@ -147,7 +181,7 @@ var pluginInstance = router.plugin(Visualizer);
147181
```
148182
149183
#### React (Declarative)
150-
184+
151185
Add the plugin to your `UIRouter` component
152186
153187
```js

src/statevis/interface.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { NodeOptions } from './tree/StateTree';
12
import { StateVisNode } from './tree/stateVisNode';
23

34
export interface NodeDimensions {
@@ -19,7 +20,7 @@ export interface Renderer {
1920
// Applies a layout to the nodes
2021
layoutFn(rootNode: StateVisNode): void;
2122
// Renders a state label
22-
labelRenderFn(x: number, y: number, node: StateVisNode, renderer: Renderer): any;
23+
labelRenderFn(x: number, y: number, node: StateVisNode, nodeOptions: NodeOptions, renderer: Renderer): any;
2324
// Renders an edge
2425
edgeRenderFn(rootNode: StateVisNode, renderer: Renderer): any;
2526

src/statevis/renderers.tsx

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import { h } from 'preact';
22
import { Renderer } from './interface';
33
import { hierarchy, cluster as d3cluster, tree as d3tree, HierarchyPointNode } from 'd3-hierarchy';
4+
import { NodeOptions } from './tree/StateTree';
45
import { StateVisNode } from './tree/stateVisNode'; // has or is using
56

6-
export const RENDERER_PRESETS = {
7+
export const RENDERER_PRESETS: { [name: string]: Partial<Renderer> } = {
78
Tree: {
89
layoutFn: TREE_LAYOUT,
910
sortNodesFn: TOP_TO_BOTTOM_SORT,
@@ -75,7 +76,7 @@ export function CLUSTER_LAYOUT(rootNode: StateVisNode) {
7576

7677
/** For RADIAL_LAYOUT: projects x/y coords from a cluster layout to circular layout */
7778
function project(x, y) {
78-
let angle = (x - 90) / 180 * Math.PI,
79+
let angle = ((x - 90) / 180) * Math.PI,
7980
radius = y;
8081
const CENTER = 0.5;
8182
return { x: CENTER + radius * Math.cos(angle), y: CENTER + radius * Math.sin(angle) };
@@ -86,13 +87,13 @@ export function RADIAL_LAYOUT(rootNode: StateVisNode) {
8687

8788
let layout = d3cluster<StateVisNode>()
8889
.size([360, 0.4])
89-
.separation(function(a, b) {
90+
.separation(function (a, b) {
9091
return (a.parent == b.parent ? 1 : 2) / a.depth;
9192
});
9293

9394
let nodes = layout(root);
9495

95-
nodes.each(function(node) {
96+
nodes.each(function (node) {
9697
let projected = project(node.x, node.y);
9798
let visNode: StateVisNode = node.data;
9899
visNode.layoutX = node.x;
@@ -104,7 +105,7 @@ export function RADIAL_LAYOUT(rootNode: StateVisNode) {
104105

105106
/** Mutates each StateVisNode by copying the new x/y values from the d3 HierarchyPointNode structure */
106107
function updateNodes(nodes: HierarchyPointNode<StateVisNode>) {
107-
nodes.each(node => {
108+
nodes.each((node) => {
108109
node.data.layoutX = node.data.x = node.x;
109110
node.data.layoutY = node.data.y = node.y;
110111
});
@@ -115,13 +116,11 @@ function updateNodes(nodes: HierarchyPointNode<StateVisNode>) {
115116
// STATE NAME LABEL
116117
///////////////////////////////////////////
117118

118-
export function RADIAL_TEXT(x, y, node: StateVisNode, renderer: Renderer) {
119+
export function RADIAL_TEXT(x, y, node: StateVisNode, nodeOptions: NodeOptions, renderer: Renderer) {
119120
let { baseFontSize, zoom } = renderer;
120121
let fontSize = baseFontSize * zoom;
121122

122-
let segments = node.name.split('.');
123-
let name = segments.pop();
124-
if (name == '**') name = segments.pop() + '.**';
123+
const label = nodeOptions?.label ? nodeOptions.label(node, defaultLabel(node)) : defaultLabel(node);
125124

126125
let angle = node.layoutX || 0;
127126

@@ -134,25 +133,30 @@ export function RADIAL_TEXT(x, y, node: StateVisNode, renderer: Renderer) {
134133
return (
135134
<text className="name" text-anchor={textAnchor} transform={transform} font-size={fontSize}>
136135
{' '}
137-
{name}{' '}
136+
{label}{' '}
138137
</text>
139138
);
140139
}
141140

142-
export function SLANTED_TEXT(x, y, node: StateVisNode, renderer: Renderer) {
143-
let { baseRadius, baseFontSize, baseStrokeWidth, baseNodeStrokeWidth, zoom } = renderer;
144-
let r = baseRadius * zoom;
145-
let fontSize = baseFontSize * zoom;
141+
export function defaultLabel(node: StateVisNode) {
146142
let segments = node.name.split('.');
147143
let name = segments.pop();
148144
if (name == '**') name = segments.pop() + '.**';
145+
return name;
146+
}
147+
148+
export function SLANTED_TEXT(x, y, node: StateVisNode, nodeOptions: NodeOptions, renderer: Renderer) {
149+
let { baseFontSize, zoom } = renderer;
150+
let fontSize = baseFontSize * zoom;
151+
152+
const label = nodeOptions?.label ? nodeOptions.label(node, defaultLabel(node)) : defaultLabel(node);
149153

150154
let transform = `rotate(-15),translate(0, ${-15 * zoom})`;
151155

152156
return (
153157
<text className="name" text-anchor="middle" transform={transform} font-size={fontSize}>
154158
{' '}
155-
{name}{' '}
159+
{label}{' '}
156160
</text>
157161
);
158162
}

src/statevis/tree/StateNode.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ export class StateNode extends Component<IProps, IState> {
5959
</text>
6060
)}
6161

62-
{renderer.labelRenderFn(x, y, node, renderer)}
62+
{renderer.labelRenderFn(x, y, node, nodeOptions, renderer)}
6363

6464
<text className="label" text-anchor="middle" font-size={fontSize} transform={`translate(0, ${r * 2})`}>
6565
{node.label}

0 commit comments

Comments
 (0)