Skip to content

Commit

Permalink
feat(statevis): allow nodes to be styled by custom classes (#197)
Browse files Browse the repository at this point in the history
* feat(statevis): allow nodes to be styled by custom classes

* feat(statevis): allow nodes to be styled by custom classes

* feat(statevis): allow nodes to be styled by custom classes
  • Loading branch information
dblVs authored and christopherthielen committed Dec 9, 2019
1 parent f242a8b commit b8f0ef6
Show file tree
Hide file tree
Showing 5 changed files with 56 additions and 28 deletions.
19 changes: 11 additions & 8 deletions src/statevis/StateVisualizer.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
import { UIRouter } from '@uirouter/core';
import { h, render, Component } from 'preact';
import { StateSelector } from '../selector/StateSelector';
import { toggleClass, addClass } from '../util/toggleClass';
import { draggable, dragActions } from '../util/draggable';
import { StateVisualizerOptions } from '../visualizer';
import { StateTree } from './tree/StateTree';
import { Controls } from './Controls';
import { StateVisWindow } from './StateVisWindow';
import { DEFAULT_RENDERER, RENDERER_PRESETS } from './renderers';
import { DEFAULT_RENDERER } from './renderers';
import { Renderer } from './interface';

declare function require(string): string;
require('./statevis.css');

export interface IProps {
router;
visualizationOptions: StateVisualizerOptions;
minimizeAfter?: number; // ms
width?: number; // px
height?: number; // px
Expand All @@ -39,6 +40,7 @@ export class StateVisualizer extends Component<IProps, IState> {
* @param element (optional) the element where the StateVisualizer should be placed.
* If no element is passed, an element will be created in the body.
* @param props height/width properties default: { height: 350, width: 250 }
* @param options StateVisualizerOptions used to customise the styling of the visualizer
*
* # Angular 1 + UI-Router (1.0.0-beta.2 and higher):
*
Expand Down Expand Up @@ -86,13 +88,13 @@ export class StateVisualizer extends Component<IProps, IState> {
* You can destroy the component using:
* [ReactDOM.unmountComponentAtNode](https://facebook.github.io/react/docs/top-level-api.html#reactdom.unmountcomponentatnode)
*/
static create(router, element?, props = {}) {
static create(router: UIRouter, element?: HTMLElement, props = {}, options?: StateVisualizerOptions) {
if (!element) {
element = document.createElement('div');
element.id = 'uirStateVisualizer';
}

let initialProps: IProps = Object.assign({}, props, { router, minimizeAfter: 2500 });
let initialProps: IProps = Object.assign({}, props, { router, minimizeAfter: 2500, visualizationOptions: options });
const _render = () => {
document.body.appendChild(element);
render(h(StateVisualizer, initialProps), element);
Expand All @@ -119,7 +121,7 @@ export class StateVisualizer extends Component<IProps, IState> {
this.setState({ renderer });
}

cancelAutoMinimize(ev) {
cancelAutoMinimize() {
if (this.minimizeTimeout) {
clearTimeout(this.minimizeTimeout);
this.minimizeTimeout = null;
Expand All @@ -130,15 +132,15 @@ export class StateVisualizer extends Component<IProps, IState> {
this.deregisterFns.forEach(fn => fn());
}

draggable(enaabled: boolean) {
draggable() {
let controlsEl = this.windowEl.querySelector('.uirStateVisControls');
let visEl = this.windowEl.querySelector('.statevis');
this.deregisterFns.push(draggable(controlsEl, dragActions.move(this.windowEl)));
this.deregisterFns.push(draggable(visEl, dragActions.move(this.windowEl)));
}

componentDidMount() {
this.draggable(true);
this.draggable();

if (this.props.minimizeAfter) {
const doMinimize = () => this.setState({ minimized: true });
Expand Down Expand Up @@ -176,6 +178,7 @@ export class StateVisualizer extends Component<IProps, IState> {

<StateTree
router={this.props.router}
nodeOptions={this.props.visualizationOptions.node}
width={this.svgWidth()}
height={this.svgHeight()}
renderer={this.state.renderer}
Expand Down
25 changes: 18 additions & 7 deletions src/statevis/tree/StateNode.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { h, render, Component } from 'preact';
import { h, Component } from 'preact';
import { Renderer } from '../interface';
import { NodeOptions } from './StateTree';
import { StateVisNode } from './stateVisNode';

export interface IProps {
router: any;
node: StateVisNode;
nodeOptions: NodeOptions;
renderer: Renderer;
doLayout: Function;
x: number;
Expand All @@ -15,13 +17,13 @@ export interface IState {}
export class StateNode extends Component<IProps, IState> {
goTimeout = null;

handleCollapseClicked = event => {
handleCollapseClicked = () => {
clearTimeout(this.goTimeout);
this.props.node._collapsed = !this.props.node._collapsed;
this.props.doLayout();
};

handleGoClicked = event => {
handleGoClicked = () => {
clearTimeout(this.goTimeout);
let stateName = this.props.node.name;
stateName = stateName.replace(/\.\*\*$/, '');
Expand All @@ -30,17 +32,26 @@ export class StateNode extends Component<IProps, IState> {

render() {
let renderer = this.props.renderer;
let { node, x, y } = this.props;
let { node, x, y, nodeOptions } = this.props;

let { baseRadius, baseFontSize, baseNodeStrokeWidth, zoom } = renderer;
let r = baseRadius * zoom;

let fontSize = baseFontSize * zoom;
let nodeStrokeWidth = baseNodeStrokeWidth * (node.entered ? 1.5 : 1) * zoom;

let classes = ['entered', 'retained', 'exited', 'active', 'inactive', 'future', 'highlight', 'collapsed'];
let circleClasses = classes.reduce((str, clazz) => str + (node[clazz] ? ` ${clazz} ` : ''), '');

let defaultClasses = [
'entered',
'retained',
'exited',
'active',
'inactive',
'future',
'highlight',
'collapsed',
].filter(clazz => node[clazz]);
let nodeClasses = nodeOptions.classes ? nodeOptions.classes(node) : '';
let circleClasses = defaultClasses + nodeClasses;
let descendents = node.collapsed ? node.totalDescendents : 0;

return (
Expand Down
26 changes: 17 additions & 9 deletions src/statevis/tree/StateTree.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,13 @@ import { easing } from '../../util/easing';
import { DEFAULT_RENDERER } from '../renderers';
import { createStateVisNode, StateVisNode } from './stateVisNode';

export interface NodeOptions {
classes?(node: StateVisNode): string;
}

export interface IProps extends NodeDimensions, VisDimensions {
router?: any;
nodeOptions?: NodeOptions;
renderer?: Renderer;
}

Expand Down Expand Up @@ -126,14 +131,16 @@ export class StateTree extends Component<IProps, IState> {
this.props.renderer.layoutFn(rootNode);

// Move all non-visible nodes to same x/y coords as the nearest visible parent
nodes.filter(node => !node.visible).forEach(node => {
let visibleAncestor = node._parent;
while (visibleAncestor && !visibleAncestor.visible) visibleAncestor = visibleAncestor._parent;
if (visibleAncestor) {
node.x = visibleAncestor.x;
node.y = visibleAncestor.y;
}
});
nodes
.filter(node => !node.visible)
.forEach(node => {
let visibleAncestor = node._parent;
while (visibleAncestor && !visibleAncestor.visible) visibleAncestor = visibleAncestor._parent;
if (visibleAncestor) {
node.x = visibleAncestor.x;
node.y = visibleAncestor.y;
}
});

let dimensions = this.dimensions();

Expand Down Expand Up @@ -180,7 +187,7 @@ export class StateTree extends Component<IProps, IState> {
500,
animationFrame,
() => null,
easing.easeInOutExpo,
easing.easeInOutExpo
);
};

Expand Down Expand Up @@ -275,6 +282,7 @@ export class StateTree extends Component<IProps, IState> {
key={node.name}
node={node}
router={this.props.router}
nodeOptions={this.props.nodeOptions}
renderer={this.props.renderer}
doLayout={this.doLayoutAnimation.bind(this)}
x={node.animX}
Expand Down
4 changes: 2 additions & 2 deletions src/statevis/tree/stateVisNode.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { StateObject } from '@uirouter/core';

export interface StateVisNode {
export interface StateVisNode extends StateObject {
name: string;

label: string;
Expand Down Expand Up @@ -40,7 +40,7 @@ export interface StateVisNode {
}

export function createStateVisNode(state: StateObject): StateVisNode {
let node: StateVisNode = Object.create(state) as any;
let node: StateVisNode = Object.create(state);

Object.defineProperty(node, 'visible', {
get() {
Expand Down
10 changes: 8 additions & 2 deletions src/visualizer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,18 @@ import { render, h } from 'preact';
import { UIRouter, UIRouterPlugin } from '@uirouter/core';
import { StateSelector } from './selector/StateSelector';
import { StateVisualizer } from './statevis/StateVisualizer';
import { StateTree } from './statevis/tree/StateTree';
import { NodeOptions, StateTree } from './statevis/tree/StateTree';
import { TransitionVisualizer } from './transition/TransitionVisualizer';

const visualizer = (router: UIRouter) => new Visualizer(router, {});

export interface StateVisualizerOptions {
node?: NodeOptions;
}

export interface Options {
state?: boolean;
stateVisualizer?: StateVisualizerOptions;
transition?: boolean;
}

Expand All @@ -19,6 +24,7 @@ function unmountComponent(node) {

const DEFAULTS = {
state: true,
stateVisualizer: {},
transition: true,
};

Expand All @@ -37,7 +43,7 @@ class Visualizer implements UIRouterPlugin {
constructor(public router: UIRouter, options: Options) {
options = Object.assign({}, DEFAULTS, options);
if (options.state) {
this.stateVisualizerEl = StateVisualizer.create(router);
this.stateVisualizerEl = StateVisualizer.create(router, undefined, undefined, options.stateVisualizer);
}
if (options.transition) {
this.transitionVisualizerEl = TransitionVisualizer.create(router);
Expand Down

0 comments on commit b8f0ef6

Please sign in to comment.