Skip to content

Commit

Permalink
0.7.0 (#67)
Browse files Browse the repository at this point in the history
* feat: implement dots background

* refactor: move background to types

* docs: add doc for dots pattern

* fix: generate unique ID for pattern

* feat: add description for interface

* docs: add dots bg to main demo

* refactor: remove zone.js

* docs: add item

* up version
  • Loading branch information
artem-mangilev authored Jun 29, 2024
1 parent 9b829d1 commit 062783b
Show file tree
Hide file tree
Showing 18 changed files with 227 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { ChangeDetectionStrategy, Component } from '@angular/core';
import { Edge, Node, VflowModule } from 'projects/ngx-vflow-lib/src/public-api';

@Component({
template: `<vflow [nodes]="nodes" [edges]="edges" background="#9de19a" />`,
template: `<vflow [nodes]="nodes" [edges]="edges" background="#bbe1fa" />`,
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true,
imports: [VflowModule]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { Edge, Node, VflowModule } from 'projects/ngx-vflow-lib/src/public-api';

@Component({
template: `<vflow
[nodes]="nodes"
[edges]="edges"
[background]="{ type: 'dots' }"
/>`,
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true,
imports: [VflowModule]
})
export class DotsCustomBackgroundDemoComponent {
public nodes: Node[] = [
{
id: '1',
point: { x: 10, y: 200 },
type: 'default',
text: '1'
},
{
id: '2',
point: { x: 200, y: 100 },
type: 'default',
text: '2'
},
{
id: '3',
point: { x: 200, y: 300 },
type: 'default',
text: '3'
},
]

public edges: Edge[] = [
{
id: '1 -> 2',
source: '1',
target: '2'
},
{
id: '1 -> 3',
source: '1',
target: '3'
},
]
}

Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
# {{ NgDocPage.title }}

You're able to select background color for your flow. Currently, it is limited to selecting a color, but in the future, it will be possible to set more complex backgrounds. To select a color, simply pass it to the `[background]` input.
You're able to select background for your flow.

## Solid color

To select a color, simply pass a color string it to the `[background]` input.

{{ NgDocActions.demo("CustomBackgroundDemoComponent", { expanded: true }) }}

## Dots pattern

To make dots pattern, pass an object to the `[background]` input according to `DotsBackground` interface

{{ NgDocActions.demo("DotsCustomBackgroundDemoComponent", { expanded: true }) }}
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { NgDocPage } from '@ng-doc/core';
import ExamplesCategory from '../../ng-doc.category'
import { CustomBackgroundDemoComponent } from './demo/custom-background-demo.component';
import { DotsCustomBackgroundDemoComponent } from './demo/dots-custom-background-demo.component';

const TestPage: NgDocPage = {
title: `Custom background`,
mdFile: './index.md',
category: ExamplesCategory,
demos: { CustomBackgroundDemoComponent },
demos: { CustomBackgroundDemoComponent, DotsCustomBackgroundDemoComponent },
order: 11
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,14 @@ import { ChangeDetectionStrategy, Component, WritableSignal, signal } from "@ang
import { VflowModule, Node, Edge, CustomNodeComponent, Connection, ComponentNode, SharedNode, DefaultNode, ConnectionSettings } from "projects/ngx-vflow-lib/src/public-api"

@Component({
template: `<vflow [nodes]="nodes" [edges]="edges" [connection]="connection" view="auto" (onConnect)="handleConnect($event)">
template: `<vflow
[nodes]="nodes"
[edges]="edges"
[connection]="connection"
[background]="{ type: 'dots' }"
view="auto"
(onConnect)="handleConnect($event)"
>
<ng-template edge let-ctx>
<svg:path
[attr.d]="ctx.path()"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@

**Great performance:** Angular signals are the heart and soul of `ngx-vflow`, which are performant by default, so you shouldn't worry about performance even with large flows.

**Zoneless:** [Does not require `zone.js`](https://stackblitz.com/edit/stackblitz-starters-qhu6im?file=src%2Fmain.ts)




Expand Down
2 changes: 1 addition & 1 deletion projects/ngx-vflow-lib/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "ngx-vflow",
"version": "0.6.0",
"version": "0.7.0",
"license": "MIT",
"homepage": "https://www.ngx-vflow.org/",
"author": "Artem Mangilev",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<ng-container *ngIf="backgroundSignal().type === 'dots'">
<svg:pattern
[attr.id]="patternId"
[attr.x]="x()"
[attr.y]="y()"
[attr.width]="scaledGap()"
[attr.height]="scaledGap()"
patternUnits="userSpaceOnUse"
>
<svg:circle
[attr.cx]="patternSize()"
[attr.cy]="patternSize()"
[attr.r]="patternSize()"
[attr.fill]="patternColor()"
/>
</svg:pattern>

<svg:rect
x="0"
y="0"
width="100%"
height="100%"
[attr.fill]="patternUrl"
/>
</ng-container>
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { Component, Input, computed, effect, inject, signal } from '@angular/core';
import { Background } from '../../types/background.type';
import { ViewportService } from '../../services/viewport.service';
import { RootSvgReferenceDirective } from '../../directives/reference.directive';
import { id } from '../../utils/id';

const defaultBg = '#fff'
const defaultGap = 20
const defaultDotSize = 2
const defaultDotColor = 'rgb(177, 177, 183)'

@Component({
selector: 'g[background]',
templateUrl: './background.component.html'
})
export class BackgroundComponent {
private viewportService = inject(ViewportService)
private rootSvg = inject(RootSvgReferenceDirective).element

@Input({ required: true, transform })
set background(value: Background) {
this.backgroundSignal.set(value)
}

protected backgroundSignal = signal<Background>({ type: 'solid', color: defaultBg })

protected scaledGap = computed(() => {
const background = this.backgroundSignal()

if (background.type === 'dots') {
const zoom = this.viewportService.readableViewport().zoom

return zoom * (background.gap ?? defaultGap)
}

return 0
})

protected x = computed(() => this.viewportService.readableViewport().x % this.scaledGap())

protected y = computed(() => this.viewportService.readableViewport().y % this.scaledGap())

protected patternColor = computed(() => this.backgroundSignal().color ?? defaultDotColor)

protected patternSize = computed(() => {
const background = this.backgroundSignal()

if (background.type === 'dots') {
return (this.viewportService.readableViewport().zoom * (background.size ?? defaultDotSize)) / 2
}

return 0
})

// Without ID there will be pattern collision for several flows on the page
// Later pattern ID may be exposed to API
protected patternId = id();
protected patternUrl = `url(#${this.patternId})`

constructor() {
effect(() => {
const background = this.backgroundSignal()

if (background.type === 'dots') {
this.rootSvg.style.backgroundColor = background.backgroundColor ?? defaultBg
}

if (background.type === 'solid') {
this.rootSvg.style.backgroundColor = background.color
}
})
}
}

function transform(background: Background | string): Background {
return typeof background === 'string'
? { type: 'solid', color: background }
: background
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Component, ElementRef, Injector, Input, NgZone, OnDestroy, OnInit, TemplateRef, inject, runInInjectionContext, signal } from '@angular/core';
import { Component, ElementRef, Injector, Input, OnDestroy, OnInit, TemplateRef, inject, runInInjectionContext, signal } from '@angular/core';
import { Position } from '../../types/position.type';
import { HandleService } from '../../services/handle.service';
import { HandleModel } from '../../models/handle.model';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { AfterViewInit, ChangeDetectionStrategy, Component, ElementRef, HostBinding, Injector, Input, NgZone, OnDestroy, OnInit, QueryList, TemplateRef, ViewChild, ViewChildren, computed, effect, inject, runInInjectionContext, signal } from '@angular/core';
import { AfterViewInit, ChangeDetectionStrategy, Component, ElementRef, Injector, Input, OnDestroy, OnInit, TemplateRef, ViewChild, ViewChildren, computed, effect, inject, runInInjectionContext, signal } from '@angular/core';
import { DraggableService } from '../../services/draggable.service';
import { NodeModel } from '../../models/node.model';
import { FlowStatusService, batchStatusChanges } from '../../services/flow-status.service';
Expand All @@ -25,7 +25,6 @@ export type HandleState = 'valid' | 'invalid' | 'idle'
export class NodeComponent implements OnInit, AfterViewInit, OnDestroy, WithInjector {
public injector = inject(Injector)
private handleService = inject(HandleService)
private zone = inject(NgZone)
private draggableService = inject(DraggableService)
private flowStatusService = inject(FlowStatusService)
private flowEntitiesService = inject(FlowEntitiesService)
Expand Down Expand Up @@ -65,7 +64,7 @@ export class NodeComponent implements OnInit, AfterViewInit, OnDestroy, WithInje
const sub = this.nodeModel.handles$
.pipe(
switchMap((handles) =>
resizable(handles.map(h => h.parentReference!), this.zone)
resizable(handles.map(h => h.parentReference!))
.pipe(map(() => handles))
),
tap((handles) => handles.forEach(h => h.updateParent()))
Expand All @@ -87,7 +86,7 @@ export class NodeComponent implements OnInit, AfterViewInit, OnDestroy, WithInje
}

if (this.nodeModel.node.type === 'html-template' || this.nodeModel.isComponentType) {
const sub = resizable([this.htmlWrapperRef.nativeElement], this.zone)
const sub = resizable([this.htmlWrapperRef.nativeElement])
.pipe(
startWith(null),
tap(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@
rootPointer
class="root-svg"
#flow
[style.backgroundColor]="background"
[attr.width]="flowWidth()"
[attr.height]="flowHeight()"
>
<defs [markers]="markers()" flowDefs />

<g [background]="background"/>

<svg:g
mapContext
spacePointContext
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import { NodeRenderingService } from '../../services/node-rendering.service';
import { SelectionService } from '../../services/selection.service';
import { FlowSettingsService } from '../../services/flow-settings.service';
import { ComponentEventBusService } from '../../services/component-event-bus.service';
import { Background } from '../../types/background.type';

const connectionControllerHostDirective = {
directive: ConnectionControllerDirective,
Expand Down Expand Up @@ -140,10 +141,10 @@ export class VflowComponent {
}

/**
* Background color for flow
* Background for flow
*/
@Input()
public background: string = '#FFFFFF'
public background: Background | string = '#fff'

/**
* Global rule if you can or can't select entities
Expand Down
29 changes: 29 additions & 0 deletions projects/ngx-vflow-lib/src/lib/vflow/types/background.type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
export type Background = ColorBackground | DotsBackground

export interface ColorBackground {
type: 'solid'
color: string
}

export interface DotsBackground {
type: 'dots'
/**
* Gap between dots
*/
gap?: number

/**
* Color of the dot
*/
color?: string

/**
* Diameter of the dot
*/
size?: number

/**
* Color behind tha dot pattern
*/
backgroundColor?: string
}
5 changes: 5 additions & 0 deletions projects/ngx-vflow-lib/src/lib/vflow/utils/id.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export function id() {
const randomLetter = String.fromCharCode(65 + Math.floor(Math.random() * 26));

return randomLetter + Date.now();
}
5 changes: 2 additions & 3 deletions projects/ngx-vflow-lib/src/lib/vflow/utils/resizable.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import { NgZone } from "@angular/core";
import { Observable } from "rxjs";

export function resizable(elems: Element[], zone: NgZone) {
export function resizable(elems: Element[]) {
return new Observable<ResizeObserverEntry[]>((subscriber) => {
let ro = new ResizeObserver((entries) => {
zone.run(() => subscriber.next(entries))
subscriber.next(entries)
});

elems.forEach(e => ro.observe(e))
Expand Down
4 changes: 3 additions & 1 deletion projects/ngx-vflow-lib/src/lib/vflow/vflow.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { HandleSizeControllerDirective } from './directives/handle-size-controll
import { SelectableDirective } from './directives/selectable.directive';
import { PointerDirective } from './directives/pointer.directive';
import { RootPointerDirective } from './directives/root-pointer.directive';
import { BackgroundComponent } from './components/background/background.component';

const components = [
VflowComponent,
Expand All @@ -24,7 +25,8 @@ const components = [
EdgeLabelComponent,
ConnectionComponent,
HandleComponent,
DefsComponent
DefsComponent,
BackgroundComponent
]

const directives = [
Expand Down
1 change: 1 addition & 0 deletions projects/ngx-vflow-lib/src/public-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export * from './lib/vflow/interfaces/component-node-event.interface';
export * from './lib/vflow/types/node-change.type';
export * from './lib/vflow/types/edge-change.type';
export * from './lib/vflow/types/position.type';
export * from './lib/vflow/types/background.type';

// Components
export * from './lib/vflow/components/vflow/vflow.component';
Expand Down

0 comments on commit 062783b

Please sign in to comment.