Skip to content

Commit

Permalink
Feat/minimap (#44)
Browse files Browse the repository at this point in the history
* feat: New minimap

* imporved preview

* better start values

* smarter destructure

* getNodesInside refactor

* refactor(minimap): add maskColor and nodeBorderRadius props

* refactor(gitignore): add dist

* refactor(minimap): show empty minimap when there are no nodes

closes #39
  • Loading branch information
AndyLnd authored and moklick committed Oct 22, 2019
1 parent 99dcc77 commit dcc38b2
Show file tree
Hide file tree
Showing 14 changed files with 2,517 additions and 496 deletions.
1,325 changes: 1,173 additions & 152 deletions dist/ReactFlow.esm.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/ReactFlow.esm.js.map

Large diffs are not rendered by default.

1,325 changes: 1,173 additions & 152 deletions dist/ReactFlow.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/ReactFlow.js.map

Large diffs are not rendered by default.

9 changes: 5 additions & 4 deletions dist/plugins/MiniMap/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import React from 'react';
import { Node } from '../../types';
declare type StringFunc = (node: Node) => string;
interface MiniMapProps extends React.HTMLAttributes<HTMLCanvasElement> {
bgColor?: string;
nodeColor?: string | StringFunc;
interface MiniMapProps extends React.HTMLAttributes<SVGSVGElement> {
nodeColor: string | StringFunc;
nodeBorderRadius: number;
maskColor: string;
}
declare const _default: ({ style, className, bgColor, nodeColor, }: MiniMapProps) => JSX.Element;
declare const _default: ({ style, className, nodeColor, nodeBorderRadius, maskColor, }: MiniMapProps) => JSX.Element;
export default _default;
14 changes: 6 additions & 8 deletions dist/types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,11 @@ export interface Dimensions {
width: number;
height: number;
}
export interface Rect extends Dimensions {
x: number;
y: number;
export interface Rect extends Dimensions, XYPosition {
}
export interface Box extends XYPosition {
x2: number;
y2: number;
}
export interface SelectionRect extends Rect {
startX: number;
Expand Down Expand Up @@ -90,13 +92,9 @@ export declare type Connection = {
target: ElementId | null;
};
export declare type OnConnectFunc = (params: Connection) => void;
export interface HandleElement {
export interface HandleElement extends XYPosition, Dimensions {
id?: ElementId | null;
position: Position;
x: number;
y: number;
width: number;
height: number;
}
export interface EdgeCompProps {
id: ElementId;
Expand Down
5 changes: 3 additions & 2 deletions dist/utils/graph.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ export declare const getOutgoers: (node: Node, elements: (Node | Edge)[]) => (No
export declare const removeElements: (elementsToRemove: (Node | Edge)[], elements: (Node | Edge)[]) => (Node | Edge)[];
export declare const addEdge: (edgeParams: Edge, elements: (Node | Edge)[]) => (Node | Edge)[];
export declare const parseElement: (element: Node | Edge, transform: [number, number, number], snapToGrid: boolean, snapGrid: [number, number]) => Node | Edge;
export declare const getBoundingBox: (nodes: Node[]) => Rect;
export declare const getBoundsofRects: (rect1: Rect, rect2: Rect) => Rect;
export declare const getRectOfNodes: (nodes: Node[]) => Rect;
export declare const graphPosToZoomedPos: (pos: XYPosition, transform: [number, number, number]) => XYPosition;
export declare const getNodesInside: (nodes: Node[], bbox: Rect, transform?: [number, number, number], partially?: boolean) => Node[];
export declare const getNodesInside: (nodes: Node[], rect: Rect, [tx, ty, tScale]?: [number, number, number], partially?: boolean) => Node[];
export declare const getConnectedEdges: (nodes: Node[], edges: Edge[]) => Edge[];
export declare const fitView: ({ padding }?: FitViewParams) => void;
export declare const zoomIn: () => void;
Expand Down
2 changes: 1 addition & 1 deletion example/src/Advanced/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ class App extends PureComponent {
snapGrid={[16, 16]}
>
<MiniMap
style={{ position: 'absolute', right: 10, bottom: 10 }}
style={{ position: 'absolute', right: 10, bottom: 10, backgroundColor: '#f8f8f8'}}
nodeColor={n => {
if (n.type === 'input') return 'blue';
if (n.type === 'output') return 'green';
Expand Down
3 changes: 2 additions & 1 deletion example/src/Empty/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { PureComponent } from 'react';

import Graph, { removeElements, addEdge, getOutgoers } from 'react-flow';
import Graph, { removeElements, addEdge, getOutgoers, MiniMap } from 'react-flow';

const onNodeDragStop = node => console.log('drag stop', node);

Expand Down Expand Up @@ -68,6 +68,7 @@ class App extends PureComponent {
style={{ width: '100%', height: '100%' }}
backgroundType="lines"
>
<MiniMap />
<button
type="button"
onClick={() => this.onAdd()}
Expand Down
5 changes: 3 additions & 2 deletions prettier.config.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
module.exports = {
trailingComma: 'es5',
singleQuote: true
};
singleQuote: true,
printWidth: 120
};
146 changes: 84 additions & 62 deletions src/plugins/MiniMap/index.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
import React, { useRef, useEffect, CSSProperties } from 'react';
import React, { CSSProperties } from 'react';
import classnames from 'classnames';

import { useStoreState } from '../../store/hooks';
import { getNodesInside } from '../../utils/graph';
import { Node } from '../../types';
import { getRectOfNodes, getBoundsofRects } from '../../utils/graph';
import { Node, Rect } from '../../types';

type StringFunc = (node: Node) => string;

interface MiniMapProps extends React.HTMLAttributes<HTMLCanvasElement> {
bgColor?: string;
nodeColor?: string | StringFunc;
interface MiniMapProps extends React.HTMLAttributes<SVGSVGElement> {
nodeColor: string | StringFunc;
nodeBorderRadius: number;
maskColor: string;
}
interface MiniMapNodeProps {
node: Node;
color: string;
borderRadius: number;
}

const baseStyle: CSSProperties = {
Expand All @@ -18,81 +24,97 @@ const baseStyle: CSSProperties = {
bottom: 10,
right: 10,
width: 200,
height: 150,
};

const MiniMapNode = ({ node, color, borderRadius }: MiniMapNodeProps) => {
const {
position: { x, y },
width,
height,
} = node.__rg;
const { background, backgroundColor } = node.style || {};
const fill = (background || backgroundColor || color) as string;
return (
<rect
className="react-flow__minimap-node"
x={x}
y={y}
rx={borderRadius}
ry={borderRadius}
width={width}
height={height}
fill={fill}
/>
);
};

export default ({
style = {},
style = { backgroundColor: '#f8f8f8' },
className,
bgColor = '#f8f8f8',
nodeColor = '#ddd',
nodeBorderRadius = 5,
maskColor = 'rgba(10, 10, 10, .25)',
}: MiniMapProps) => {
const canvasNode = useRef<HTMLCanvasElement>(null);
const state = useStoreState(s => ({
width: s.width,
height: s.height,
nodes: s.nodes,
transform: s.transform,
const state = useStoreState(({ width, height, nodes, transform: [tX, tY, tScale] }) => ({
width,
height,
nodes,
tX,
tY,
tScale,
}));
const mapClasses = classnames('react-flow__minimap', className);
const nodePositions = state.nodes.map(n => n.__rg.position);
const width: number = +(style.width || baseStyle.width || 0);
const height = (state.height / (state.width || 1)) * width;
const bbox = { x: 0, y: 0, width: state.width, height: state.height };
const scaleFactor = width / state.width;
const nodeColorFunc = (nodeColor instanceof Function
? nodeColor
: () => nodeColor) as StringFunc;

useEffect(() => {
if (!canvasNode || !canvasNode.current) {
return;
}

const ctx = canvasNode.current.getContext('2d');

if (!ctx) {
return;
}
const mapClasses = classnames('react-flow__minimap', className);
const elementWidth = (style.width || baseStyle.width)! as number;
const elementHeight = (style.height || baseStyle.height)! as number;
const nodeColorFunc = (nodeColor instanceof Function ? nodeColor : () => nodeColor) as StringFunc;
const hasNodes = state.nodes && state.nodes.length;

const nodesInside = getNodesInside(
state.nodes,
bbox,
state.transform,
true
);
const bb = getRectOfNodes(state.nodes);
const viewBB: Rect = {
x: -state.tX / state.tScale,
y: -state.tY / state.tScale,
width: state.width / state.tScale,
height: state.height / state.tScale,
};

ctx.fillStyle = bgColor;
ctx.fillRect(0, 0, width, height);
const boundingRect = hasNodes ? getBoundsofRects(bb, viewBB) : viewBB;

nodesInside.forEach(n => {
const pos = n.__rg.position;
const transformX = state.transform[0];
const transformY = state.transform[1];
const x = pos.x * state.transform[2] + transformX;
const y = pos.y * state.transform[2] + transformY;
const scaledWidth = boundingRect.width / elementWidth;
const scaledHeight = boundingRect.height / elementHeight;
const viewScale = Math.max(scaledWidth, scaledHeight);
const viewWidth = viewScale * elementWidth;
const viewHeight = viewScale * elementHeight;

ctx.fillStyle = nodeColorFunc(n);
const offset = 5 * viewScale;

ctx.fillRect(
x * scaleFactor,
y * scaleFactor,
n.__rg.width * scaleFactor * state.transform[2],
n.__rg.height * scaleFactor * state.transform[2]
);
});
}, [canvasNode.current, nodePositions, state.transform, height]);
const x = boundingRect.x - (viewWidth - boundingRect.width) / 2 - offset;
const y = boundingRect.y - (viewHeight - boundingRect.height) / 2 - offset;
const width = viewWidth + offset * 2;
const height = viewHeight + offset * 2;

return (
<canvas
<svg
width={elementWidth}
height={elementHeight}
viewBox={`${x} ${y} ${width} ${height}`}
style={{
...baseStyle,
...style,
height,
}}
width={width}
height={height}
className={mapClasses}
ref={canvasNode}
/>
>
{state.nodes.map(node => (
<MiniMapNode key={node.id} node={node} color={nodeColorFunc(node)} borderRadius={nodeBorderRadius} />
))}
<path
className="react-flow__minimap-mask"
d={`M${x - offset},${y - offset}h${width + offset * 2}v${height + offset * 2}h${-width - offset * 2}z
M${viewBB.x},${viewBB.y}h${viewBB.width}v${viewBB.height}h${-viewBB.width}z`}
fill={maskColor}
fillRule="evenodd"
/>
</svg>
);
};
4 changes: 2 additions & 2 deletions src/store/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ import isEqual from 'fast-deep-equal';
import { Selection as D3Selection, ZoomBehavior } from 'd3';

import {
getBoundingBox,
getNodesInside,
getConnectedEdges,
getRectOfNodes,
} from '../utils/graph';
import {
ElementId,
Expand Down Expand Up @@ -200,7 +200,7 @@ const storeModel: StoreModel = {
selection,
state.transform
);
const selectedNodesBbox = getBoundingBox(selectedNodes);
const selectedNodesBbox = getRectOfNodes(selectedNodes);

state.selection = selection;
state.nodesSelectionActive = true;
Expand Down
14 changes: 6 additions & 8 deletions src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,11 @@ export interface Dimensions {
height: number;
}

export interface Rect extends Dimensions {
x: number;
y: number;
export interface Rect extends Dimensions, XYPosition {}

export interface Box extends XYPosition {
x2: number;
y2: number;
}

export interface SelectionRect extends Rect {
Expand Down Expand Up @@ -112,13 +114,9 @@ export type Connection = {

export type OnConnectFunc = (params: Connection) => void;

export interface HandleElement {
export interface HandleElement extends XYPosition, Dimensions {
id?: ElementId | null;
position: Position;
x: number;
y: number;
width: number;
height: number;
}

export interface EdgeCompProps {
Expand Down

0 comments on commit dcc38b2

Please sign in to comment.