UNPKG

cytoscape-angular-ng17

Version:
821 lines (816 loc) 91.7 kB
import * as i0 from '@angular/core'; import { Component, ViewChild, Input, EventEmitter, Output, NgModule } from '@angular/core'; import cytoscape from 'cytoscape'; import * as i1 from '@angular/common'; import { CommonModule } from '@angular/common'; import * as i2 from 'primeng/progressspinner'; import { ProgressSpinnerModule } from 'primeng/progressspinner'; import * as i3 from 'primeng/button'; import { ButtonModule } from 'primeng/button'; import * as i3$1 from 'primeng/api'; import * as i4$2 from 'primeng/overlaypanel'; import { OverlayPanelModule } from 'primeng/overlaypanel'; import * as i8 from 'primeng/tooltip'; import { TooltipModule } from 'primeng/tooltip'; import * as i2$1 from '@angular/forms'; import { FormControl, FormGroup, FormsModule, ReactiveFormsModule } from '@angular/forms'; import * as i4 from 'primeng/dropdown'; import { DropdownModule } from 'primeng/dropdown'; import * as i5 from 'primeng/fieldset'; import { FieldsetModule } from 'primeng/fieldset'; import * as i6 from 'primeng/inputswitch'; import { InputSwitchModule } from 'primeng/inputswitch'; import * as i7 from 'primeng/inputtext'; import { InputTextModule } from 'primeng/inputtext'; import * as i4$1 from 'primeng/autocomplete'; import { AutoCompleteModule } from 'primeng/autocomplete'; import { ProgressBarModule } from 'primeng/progressbar'; import { SpinnerModule } from 'primeng/spinner'; /** * The API is a little odd to provide flexibility. * EITHER bind to cyOptions (type CytoscapeOptions), to control the options yourself * OR this component will build a CytoscapeOptions internally by using all the other inputs. * If cyOptions is supplied, all other inputs are ignored. * The cyOptions container (HTML element) is always ignored and set internally. */ class CytoscapeGraphComponent { cyGraph; debug = false; nodes; edges; autolock; autoungrabify; autounselectify; boxSelectionEnabled; desktopTapThreshold; hideEdgesOnViewport; hideLabelsOnViewport; layoutOptions; maxZoom; minZoom; motionBlur; motionBlurOpacity; pan; panningEnabled; pixelRatio; selectionType; style; styleEnabled; textureOnViewport; touchTapThreshold; userPanningEnabled; userZoomingEnabled; wheelSensitivity; zoom; zoomingEnabled; showToolbar = true; cyOptions; cy; loading = false; constructor() { } ngOnChanges(changes) { console.log('cytoscape graph component ngOnChanges. changes:', JSON.stringify(changes)); if (changes['style']) { console.log('changes["style"]:', JSON.stringify(changes['style'])); this.runWhileLoading(this.updateStyles.bind(this)); } } getCy() { return this.cy; } centerElements(selector) { if (!this.cy) { return; } const elems = this.cy.$(selector); this.cy.center(elems); } zoomToElement(selector, level = 3) { let position = this.cy?.$(selector)?.position(); if (!position) { console.warn(`Cannot zoom to ${selector}`); } this.cy.zoom({ level: level, position: position, }); } render() { this.runWhileLoading(this.rerender.bind(this)); } runWhileLoading(f) { this.loading = true; setTimeout(() => { f(); setTimeout(() => { this.loading = false; }, 30); }, 0); } updateStyles() { if (this.cy && this.style) { this.cy.style(this.style); } } rerender() { //TODO : this takes a heavy-handed approach, refine for performance if (!this.cyGraph) { console.warn(`No cyGraph found`); return; } const cyOptions = this.cyOptions || { // ignored, use nodes and edges // elements: this.elements, autolock: this.autolock, autoungrabify: this.autoungrabify, autounselectify: this.autounselectify, boxSelectionEnabled: this.boxSelectionEnabled, container: this.cyGraph.nativeElement, desktopTapThreshold: this.desktopTapThreshold, hideEdgesOnViewport: this.hideEdgesOnViewport, hideLabelsOnViewport: this.hideLabelsOnViewport, layout: this.layoutOptions, maxZoom: this.maxZoom, minZoom: this.minZoom, motionBlur: this.motionBlur, motionBlurOpacity: this.motionBlurOpacity, pan: this.pan, panningEnabled: this.panningEnabled, pixelRatio: this.pixelRatio, selectionType: this.selectionType, style: this.style, styleEnabled: this.styleEnabled, textureOnViewport: this.textureOnViewport, touchTapThreshold: this.touchTapThreshold, userPanningEnabled: this.userPanningEnabled, userZoomingEnabled: this.userZoomingEnabled, wheelSensitivity: this.wheelSensitivity, zoomingEnabled: this.zoomingEnabled, zoom: this.zoom, }; // TODO do reset() instead? if (!this.cy) { this.cy = cytoscape(cyOptions); } this.cy.startBatch(); this.cy.boxSelectionEnabled(this.boxSelectionEnabled); this.cy.nodes().remove(); this.cy.edges().remove(); if (this.nodes) { this.cy.add(this.nodes); } if (this.edges) { this.cy.add(this.edges); } this.cy.endBatch(); if (this.layoutOptions) { this.cy.layout(this.layoutOptions).run(); } } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.3", ngImport: i0, type: CytoscapeGraphComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "17.3.3", type: CytoscapeGraphComponent, selector: "cytoscape-graph", inputs: { debug: "debug", nodes: "nodes", edges: "edges", autolock: "autolock", autoungrabify: "autoungrabify", autounselectify: "autounselectify", boxSelectionEnabled: "boxSelectionEnabled", desktopTapThreshold: "desktopTapThreshold", hideEdgesOnViewport: "hideEdgesOnViewport", hideLabelsOnViewport: "hideLabelsOnViewport", layoutOptions: "layoutOptions", maxZoom: "maxZoom", minZoom: "minZoom", motionBlur: "motionBlur", motionBlurOpacity: "motionBlurOpacity", pan: "pan", panningEnabled: "panningEnabled", pixelRatio: "pixelRatio", selectionType: "selectionType", style: "style", styleEnabled: "styleEnabled", textureOnViewport: "textureOnViewport", touchTapThreshold: "touchTapThreshold", userPanningEnabled: "userPanningEnabled", userZoomingEnabled: "userZoomingEnabled", wheelSensitivity: "wheelSensitivity", zoom: "zoom", zoomingEnabled: "zoomingEnabled", showToolbar: "showToolbar" }, viewQueries: [{ propertyName: "cyGraph", first: true, predicate: ["cyGraph"], descendants: true }], usesOnChanges: true, ngImport: i0, template: ` <p-progressSpinner *ngIf="loading" class="spinner" strokeWidth="4" fill="#EEEEEE" animationDuration=".5s" ></p-progressSpinner> <div #cyGraph class="graphWrapper"></div> `, isInline: true, styles: [".spinner{position:absolute;left:\"350px\";z-index:10;width:\"250px\";height:\"250px\"}@keyframes ui-progress-spinner-color{to,0%{stroke:#d62d20}40%{stroke:#0057e7}66%{stroke:#008744}80%,90%{stroke:#ffa700}}.graphWrapper{height:100%;width:100%}\n"], dependencies: [{ kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: i2.ProgressSpinner, selector: "p-progressSpinner", inputs: ["styleClass", "style", "strokeWidth", "fill", "animationDuration", "ariaLabel"] }] }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.3", ngImport: i0, type: CytoscapeGraphComponent, decorators: [{ type: Component, args: [{ selector: 'cytoscape-graph', template: ` <p-progressSpinner *ngIf="loading" class="spinner" strokeWidth="4" fill="#EEEEEE" animationDuration=".5s" ></p-progressSpinner> <div #cyGraph class="graphWrapper"></div> `, styles: [".spinner{position:absolute;left:\"350px\";z-index:10;width:\"250px\";height:\"250px\"}@keyframes ui-progress-spinner-color{to,0%{stroke:#d62d20}40%{stroke:#0057e7}66%{stroke:#008744}80%,90%{stroke:#ffa700}}.graphWrapper{height:100%;width:100%}\n"] }] }], ctorParameters: () => [], propDecorators: { cyGraph: [{ type: ViewChild, args: ['cyGraph'] }], debug: [{ type: Input }], nodes: [{ type: Input }], edges: [{ type: Input }], autolock: [{ type: Input }], autoungrabify: [{ type: Input }], autounselectify: [{ type: Input }], boxSelectionEnabled: [{ type: Input }], desktopTapThreshold: [{ type: Input }], hideEdgesOnViewport: [{ type: Input }], hideLabelsOnViewport: [{ type: Input }], layoutOptions: [{ type: Input }], maxZoom: [{ type: Input }], minZoom: [{ type: Input }], motionBlur: [{ type: Input }], motionBlurOpacity: [{ type: Input }], pan: [{ type: Input }], panningEnabled: [{ type: Input }], pixelRatio: [{ type: Input }], selectionType: [{ type: Input }], style: [{ type: Input }], styleEnabled: [{ type: Input }], textureOnViewport: [{ type: Input }], touchTapThreshold: [{ type: Input }], userPanningEnabled: [{ type: Input }], userZoomingEnabled: [{ type: Input }], wheelSensitivity: [{ type: Input }], zoom: [{ type: Input }], zoomingEnabled: [{ type: Input }], showToolbar: [{ type: Input }] } }); class BaseLayoutOptionsImpl { ready(e) { // tslint:disable-next-line:no-console console.debug(`layout ready, cytoscape.LayoutEventObject: ${JSON.stringify(e)}`); // on layoutready } stop(e) { // tslint:disable-next-line:no-console console.debug(`layout stop, cytoscape.LayoutEventObject: ${JSON.stringify(e)}`); // on layoutstop } } class NullLayoutOptionsImpl extends BaseLayoutOptionsImpl { name = 'null'; } class AnimateLayoutOptionsImpl extends BaseLayoutOptionsImpl { // the zoom level to set (prob want fit = false if set) zoom = undefined; // the pan level to set (prob want fit = false if set) pan = undefined; // whether to transition the node positions animate = false; // duration of animation in ms if enabled animationDuration = 500; // easing of animation if enabled animationEasing = undefined; // a function that determines whether the node should be animated. // All nodes animated by default on animate enabled. Non-animated nodes are // positioned immediately when the layout starts animateFilter = (node, i) => true; } class PresetLayoutOptionsImpl extends AnimateLayoutOptionsImpl { name = 'preset'; fit; padding; // map of (node id) => (position obj); or function(node){ return somPos; } positions; // transform a given node position. Useful for changing flow direction in discrete layouts transform = (node, position) => position; } class ShapedLayoutOptionsImpl extends AnimateLayoutOptionsImpl { // whether to fit to viewport fit = true; // fit padding padding = 30; // constrain layout bounds boundingBox = undefined; // prevents node overlap, may overflow boundingBox if not enough space avoidOverlap = true; // Excludes the label when calculating node bounding boxes for the layout algorithm nodeDimensionsIncludeLabels = false; // Applies a multiplicative factor (>0) to expand or compress the overall area that the nodes take up spacingFactor = 1.75; // a sorting function to order the nodes; e.g. function(a, b){ return a.data('weight') - b.data('weight') } sort = undefined; // transform a given node position. Useful for changing flow direction in discrete layouts transform = (node, position) => position; } class GridLayoutOptionsImpl extends ShapedLayoutOptionsImpl { name = 'grid'; // extra spacing around nodes when avoidOverlap: true avoidOverlapPadding = 10; // uses all available space on false, uses minimal space on true condense = false; // force num of rows in the grid rows = undefined; // force num of columns in the grid cols = undefined; // returns { row, col } for element // (node: NodeSingular) => return { row: number; col: number; } position = null; } class RandomLayoutOptionsImpl extends AnimateLayoutOptionsImpl { name = 'random'; fit = true; padding = 20; // constrain layout bounds; { x1, y1, x2, y2 } or { x1, y1, w, h } boundingBox = undefined; // transform a given node position. Useful for changing flow direction in discrete layouts transform = (node, position) => position; } class CircleLayoutOptionsImpl extends ShapedLayoutOptionsImpl { name = 'circle'; radius; // the radius of the circle startAngle = (3 / 2) * Math.PI; // where nodes start in radians sweep = undefined; // how many radians should be between the first and last node (defaults to full circle) clockwise; // whether the layout should go clockwise (true) or counterclockwise/anticlockwise (false) } // Note: "radius" is not part of concentric, imperfect extension class ConcentricLayoutOptionsImpl { name = 'concentric'; // how many radians should be between the first and last node (defaults to full circle) sweep; // whether the layout should go clockwise (true) or counterclockwise/anticlockwise (false) clockwise; // where nodes start in radians, e.g. 3 / 2 * Math.PI, startAngle = (3 / 2) * Math.PI; fit; nodeDimensionsIncludeLabels; equidistant; // whether levels have an equal radial distance betwen them, may cause bounding box overflow minNodeSpacing = 10; // min spacing between outside of nodes (used for radius adjustment) boundingBox; // constrain layout bounds; { x1, y1, x2, y2 } or { x1, y1, w, h } // height of layout area (overrides container height) height = null; // width of layout area (overrides container width) width = null; // Applies a multiplicative factor (>0) to expand or compress the overall area that the nodes take up spacingFactor; concentric(node) { return 0; } levelWidth(node) { return 0; } } class BreadthFirstLayoutOptionsImpl extends ShapedLayoutOptionsImpl { name = 'breadthfirst'; // whether the tree is directed downwards (or edges can point in any direction if false) directed = false; // put depths in concentric circles if true, put depths top down if false circle = false; // the roots of the trees roots; // how many times to try to position the nodes in a maximal way (i.e. no backtracking) maximalAdjustments; // whether to shift nodes down their natural BFS depths in order to avoid upwards edges (DAGS only) maximal = false; grid = false; // whether to create an even grid into which the DAG is placed (circle:false only) nodeDimensionsIncludeLabels = false; // Excludes the label when calculating node bounding boxes for the layout algorithm } class CoseLayoutOptionsImpl extends ShapedLayoutOptionsImpl { name = 'cose'; // The layout animates only after this many milliseconds for animate:true // (prevents flashing on fast runs) animationThreshold = 250; // Number of iterations between consecutive screen positions update refresh = 20; // Randomize the initial positions of the nodes (true) or use existing positions (false) randomize = false; // Extra spacing between components in non-compound graphs componentSpacing = 40; // Node repulsion (overlapping) multiplier nodeOverlap = 4; // Nesting factor (multiplier) to compute ideal edge length for nested edges nestingFactor = 1.2; // Gravity force (constant) gravity = 1; // Maximum number of iterations to perform numIter = 1000; // Initial temperature (maximum node displacement) initialTemp = 1000; // Cooling factor (how the temperature is reduced between consecutive iterations coolingFactor = 0.99; // Lower temperature threshold (below this point the layout will end) minTemp = 1.0; // Node repulsion (non overlapping) multiplier nodeRepulsion = (node) => 2048; // Ideal edge (non nested) length idealEdgeLength = (edge) => 32; // Divisor to compute edge forces edgeElasticity = (edge) => 32; } class DagreLayoutOptionsImpl extends ShapedLayoutOptionsImpl { constructor() { super(); } name = 'dagre'; nodeSep = undefined; // the separation between adjacent nodes in the same rank edgeSep = undefined; // the separation between adjacent edges in the same rank rankSep = undefined; // the separation between each rank in the layout // TB for top to bottom flow, 'LR' for left to right rankDir = 'TB'; // Type of algorithm to assign a rank to each node in the input graph. // Possible values: 'network-simplex', 'tight-tree' or 'longest-path' ranker = undefined; // number of ranks to keep between the source and target of the edge minLen = (edge) => 1; edgeWeight = (edge) => 1; // higher weight edges are generally made shorter and straighter than lower weight edges } class FormInfo { title; fieldsets; showSubmitButton; submitText; disableSubmitOnFormInvalid; otherFieldsetTitle; constructor(title, fieldsets, showSubmitButton = false, submitText = 'Submit', disableSubmitOnFormInvalid = false, /* if the model has a property that isn't in a fieldset, but it in an fieldset created by the form */ otherFieldsetTitle = null) { this.title = title; this.fieldsets = fieldsets; this.showSubmitButton = showSubmitButton; this.submitText = submitText; this.disableSubmitOnFormInvalid = disableSubmitOnFormInvalid; this.otherFieldsetTitle = otherFieldsetTitle; } } class FieldsetInfo { legend; fieldInfos; displayOnlyIfProperties; constructor(legend, fieldInfos, displayOnlyIfProperties) { this.legend = legend; this.fieldInfos = fieldInfos; this.displayOnlyIfProperties = displayOnlyIfProperties; } showFieldsetForModel(model) { if (!this.displayOnlyIfProperties) { return true; } for (const fieldInfo of this.fieldInfos) { for (const modelProperty of Object.keys(model)) { if (fieldInfo.modelProperty === modelProperty) { return true; } } } return false; } } class FieldInfo { label; modelProperty; type; tooltip; validators; disableWhenInvalid; hideWhenNoModelProperty; placeholder; inputType; inputSize; options; optionArrayLabelField; optionArrayValueField; fieldTypes = {}; updateOn = 'submit'; // same as AbstractControlOptions asyncValidators; constructor( /* label to show the user next to the field, can be a function for i18n/dynamic labels */ label, /* The form has a model, this is the name of the property on the form's model object that this field */ modelProperty, /* computed from the model property type by default */ type, /* The tooltip to display on hover */ tooltip, /* The list of Angular Form Validators for the control or a function that returns such an array */ validators, /* disable the field if it's not valid */ disableWhenInvalid = false, /* If true and model[modelProperty] is undefined, don't create a field.*/ hideWhenNoModelProperty = true, /* Input only - by default the label is used as a placeholder and floats (how to downcast in a template?) */ placeholder, /* Input only - same as HTML input (how to downcast in a template?) */ inputType = 'text', /* Input only - same as HTML input (how to downcast in a template?) */ inputSize = 8, /* Select only either an array of object or the name of a model property or function that is/returns an array of objects */ options, /* In an options object, what field to display to the user (or function that returns a string given the option object and the model) */ optionArrayLabelField, /* In an options object, what field to return for the value of the option (or function that returns a string given the option object and the model) */ optionArrayValueField) { this.label = label; this.modelProperty = modelProperty; this.type = type; this.tooltip = tooltip; this.validators = validators; this.disableWhenInvalid = disableWhenInvalid; this.hideWhenNoModelProperty = hideWhenNoModelProperty; this.placeholder = placeholder; this.inputType = inputType; this.inputSize = inputSize; this.options = options; this.optionArrayLabelField = optionArrayLabelField; this.optionArrayValueField = optionArrayValueField; } fieldType(model) { if (!this.modelProperty) { return this.fieldTypes[0]; // Return first field type if no model property } const cached = this.fieldTypes[this.modelProperty]; if (cached) { return cached; } else { const fieldValueType = typeof model[this.modelProperty]; const result = this.type ? this.type : this.options ? 'options' : fieldValueType; this.fieldTypes[this.modelProperty] = result; return result; } } setValue(newValue, model, modelChange) { if (!this.modelProperty) { return; // cannot set values that don't have a model property } model[this.modelProperty] = newValue; modelChange.emit({ property: this.modelProperty, value: newValue }); } } class FluidFormComponent { model; modelChange = new EventEmitter(); modelProperty; formInfo; formGroup; constructor() { } ngOnInit() { console.debug('FluidFormComponent this.formInfo:', JSON.stringify(this.formInfo)); let controls = {}; this.formInfo?.fieldsets.forEach((fieldsetInfo) => { fieldsetInfo.fieldInfos.forEach((fieldInfo) => { if (!this.model || !fieldInfo || !fieldInfo.modelProperty) { return; } let modelValue = this.model[fieldInfo.modelProperty]; // console.log('fieldInfo.modelProperty:', fieldInfo.modelProperty, ', modelValue:', modelValue) const validators = typeof fieldInfo.validators === 'function' ? fieldInfo.validators() : fieldInfo.validators; const asyncValidators = typeof fieldInfo.asyncValidators === 'function' ? fieldInfo.asyncValidators() : fieldInfo.asyncValidators; const { updateOn } = fieldInfo; let formControl = new FormControl(modelValue, { validators, asyncValidators, updateOn, }); formControl.valueChanges.subscribe((change) => { if (!this.model || !fieldInfo || !fieldInfo.modelProperty) { return; } console.debug('form control change ', JSON.stringify(change), ' for prop ', fieldInfo.modelProperty, ', changing current model value ', this.model[fieldInfo.modelProperty], ' to ', change); fieldInfo.setValue(change, this.model, this.modelChange); }); controls[fieldInfo.modelProperty] = formControl; }); }); this.formGroup = new FormGroup(controls); } ngOnChanges(changes) { console.debug('ngOnChanges fluid-form changes:', JSON.stringify(changes)); if (changes['model']) { const model = changes['model'].currentValue; for (let key of Object.keys(model)) { console.debug('ngOnChanges model key copying to form:', key); const control = this.formGroup?.controls[key]; control ? control.setValue(model[key], { emitEvent: false }) : console.warn('no control for model key ', key); } } } ngAfterViewInit() { // console.debug("ngAfterViewInit") } ngAfterViewChecked() { // console.debug("ngAfterViewChecked") } onSubmit() { console.log(`Form submitted`); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.3", ngImport: i0, type: FluidFormComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "17.3.3", type: FluidFormComponent, selector: "cyng-fluid-form", inputs: { model: "model", modelProperty: "modelProperty", formInfo: "formInfo" }, outputs: { modelChange: "modelChange" }, usesOnChanges: true, ngImport: i0, template: "<form [formGroup]=\"formGroup\" [title]=\"formInfo?.title\" (ngSubmit)=\"onSubmit()\">\n <ng-container *ngFor=\"let fieldSetInfo of formInfo.fieldsets\">\n <p-fieldset\n *ngIf=\"model && fieldSetInfo.showFieldsetForModel(model)\"\n class=\"fieldset\"\n legend=\"{{ fieldSetInfo.legend }}\"\n >\n <div class=\"ui-g ui-fluid\">\n <div\n class=\"ui-g-12 ui-md-4 field\"\n *ngFor=\"let fieldInfo of fieldSetInfo.fieldInfos\"\n >\n <div class=\"ui-inputgroup\">\n <ng-container *ngIf=\"fieldInfo.fieldType(model) === 'boolean'\">\n <span class=\"ui-chkbox-label\">\n {{ fieldInfo.label }}\n </span>\n <p-inputSwitch\n name=\"{{ fieldInfo.modelProperty }}\"\n pTooltip=\"{{ fieldInfo.tooltip }}\"\n formControlName=\"{{ fieldInfo.modelProperty }}\"\n >\n </p-inputSwitch>\n </ng-container>\n <ng-container\n *ngIf=\"\n fieldInfo.fieldType(model) === 'string' ||\n fieldInfo.fieldType(model) === 'number'\n \"\n >\n <span class=\"ui-float-label\">\n <input\n pInputText\n id=\"{{ fieldInfo.modelProperty }}\"\n name=\"{{ fieldInfo.modelProperty }}\"\n formControlName=\"{{ fieldInfo.modelProperty }}\"\n [pTooltip]=\"fieldInfo.tooltip\"\n [type]=\"fieldInfo.inputType\"\n [size]=\"fieldInfo.inputSize\"\n />\n <label for=\"{{ fieldInfo.modelProperty }}\">{{\n fieldInfo.label\n }}</label>\n </span>\n </ng-container>\n <ng-container\n *ngIf=\"\n fieldInfo.fieldType(model) === 'options' &&\n fieldInfo.options &&\n fieldInfo.optionArrayLabelField\n \"\n >\n <span class=\"ui-float-label\">\n <p-dropdown\n formControlName=\"{{ fieldInfo.modelProperty }}\"\n [name]=\"fieldInfo.modelProperty\"\n [options]=\"fieldInfo.options\"\n [optionLabel]=\"fieldInfo.optionArrayLabelField\"\n [pTooltip]=\"fieldInfo.tooltip\"\n ></p-dropdown>\n <label for=\"{{ fieldInfo.modelProperty }}\">{{\n fieldInfo.label\n }}</label>\n </span>\n </ng-container>\n </div>\n </div>\n </div>\n </p-fieldset>\n </ng-container>\n</form>\n<button\n *ngIf=\"formInfo.showSubmitButton\"\n pButton\n [disabled]=\"formInfo.disableSubmitOnFormInvalid && !formGroup.valid\"\n (submit)=\"onSubmit()\"\n>\n {{ formInfo.submitText || \"Submit\" }}\n</button>\n", styles: [".ui-chkbox-label{padding-right:.5em}.ui-dropdown-label{align-self:center;padding-right:.5em}.field:nth-child(n+4){margin-top:1em}\n"], dependencies: [{ kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i2$1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i2$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i2$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i2$1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "directive", type: i3.ButtonDirective, selector: "[pButton]", inputs: ["iconPos", "loadingIcon", "label", "icon", "loading"] }, { kind: "component", type: i4.Dropdown, selector: "p-dropdown", inputs: ["id", "scrollHeight", "filter", "name", "style", "panelStyle", "styleClass", "panelStyleClass", "readonly", "required", "editable", "appendTo", "tabindex", "placeholder", "filterPlaceholder", "filterLocale", "inputId", "dataKey", "filterBy", "filterFields", "autofocus", "resetFilterOnHide", "dropdownIcon", "optionLabel", "optionValue", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "autoDisplayFirst", "group", "showClear", "emptyFilterMessage", "emptyMessage", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "overlayOptions", "ariaFilterLabel", "ariaLabel", "ariaLabelledBy", "filterMatchMode", "maxlength", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "focusOnHover", "selectOnFocus", "autoOptionFocus", "autofocusFilter", "disabled", "itemSize", "autoZIndex", "baseZIndex", "showTransitionOptions", "hideTransitionOptions", "filterValue", "options"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onShow", "onHide", "onClear", "onLazyLoad"] }, { kind: "component", type: i5.Fieldset, selector: "p-fieldset", inputs: ["legend", "toggleable", "collapsed", "style", "styleClass", "transitionOptions"], outputs: ["collapsedChange", "onBeforeToggle", "onAfterToggle"] }, { kind: "component", type: i6.InputSwitch, selector: "p-inputSwitch", inputs: ["style", "styleClass", "tabindex", "inputId", "name", "disabled", "readonly", "trueValue", "falseValue", "ariaLabel", "ariaLabelledBy"], outputs: ["onChange"] }, { kind: "directive", type: i7.InputText, selector: "[pInputText]" }, { kind: "directive", type: i8.Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "appendTo", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "pTooltip", "tooltipDisabled", "tooltipOptions"] }] }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.3", ngImport: i0, type: FluidFormComponent, decorators: [{ type: Component, args: [{ selector: 'cyng-fluid-form', template: "<form [formGroup]=\"formGroup\" [title]=\"formInfo?.title\" (ngSubmit)=\"onSubmit()\">\n <ng-container *ngFor=\"let fieldSetInfo of formInfo.fieldsets\">\n <p-fieldset\n *ngIf=\"model && fieldSetInfo.showFieldsetForModel(model)\"\n class=\"fieldset\"\n legend=\"{{ fieldSetInfo.legend }}\"\n >\n <div class=\"ui-g ui-fluid\">\n <div\n class=\"ui-g-12 ui-md-4 field\"\n *ngFor=\"let fieldInfo of fieldSetInfo.fieldInfos\"\n >\n <div class=\"ui-inputgroup\">\n <ng-container *ngIf=\"fieldInfo.fieldType(model) === 'boolean'\">\n <span class=\"ui-chkbox-label\">\n {{ fieldInfo.label }}\n </span>\n <p-inputSwitch\n name=\"{{ fieldInfo.modelProperty }}\"\n pTooltip=\"{{ fieldInfo.tooltip }}\"\n formControlName=\"{{ fieldInfo.modelProperty }}\"\n >\n </p-inputSwitch>\n </ng-container>\n <ng-container\n *ngIf=\"\n fieldInfo.fieldType(model) === 'string' ||\n fieldInfo.fieldType(model) === 'number'\n \"\n >\n <span class=\"ui-float-label\">\n <input\n pInputText\n id=\"{{ fieldInfo.modelProperty }}\"\n name=\"{{ fieldInfo.modelProperty }}\"\n formControlName=\"{{ fieldInfo.modelProperty }}\"\n [pTooltip]=\"fieldInfo.tooltip\"\n [type]=\"fieldInfo.inputType\"\n [size]=\"fieldInfo.inputSize\"\n />\n <label for=\"{{ fieldInfo.modelProperty }}\">{{\n fieldInfo.label\n }}</label>\n </span>\n </ng-container>\n <ng-container\n *ngIf=\"\n fieldInfo.fieldType(model) === 'options' &&\n fieldInfo.options &&\n fieldInfo.optionArrayLabelField\n \"\n >\n <span class=\"ui-float-label\">\n <p-dropdown\n formControlName=\"{{ fieldInfo.modelProperty }}\"\n [name]=\"fieldInfo.modelProperty\"\n [options]=\"fieldInfo.options\"\n [optionLabel]=\"fieldInfo.optionArrayLabelField\"\n [pTooltip]=\"fieldInfo.tooltip\"\n ></p-dropdown>\n <label for=\"{{ fieldInfo.modelProperty }}\">{{\n fieldInfo.label\n }}</label>\n </span>\n </ng-container>\n </div>\n </div>\n </div>\n </p-fieldset>\n </ng-container>\n</form>\n<button\n *ngIf=\"formInfo.showSubmitButton\"\n pButton\n [disabled]=\"formInfo.disableSubmitOnFormInvalid && !formGroup.valid\"\n (submit)=\"onSubmit()\"\n>\n {{ formInfo.submitText || \"Submit\" }}\n</button>\n", styles: [".ui-chkbox-label{padding-right:.5em}.ui-dropdown-label{align-self:center;padding-right:.5em}.field:nth-child(n+4){margin-top:1em}\n"] }] }], ctorParameters: () => [], propDecorators: { model: [{ type: Input }], modelChange: [{ type: Output }], modelProperty: [{ type: Input }], formInfo: [{ type: Input }] } }); class CytoscapeLayoutToolComponent { static LAYOUT_FORM_INFO = CytoscapeLayoutToolComponent.createLayoutFormInfo(); layoutForm; _layoutOptions; changed = false; get layoutOptions() { return this._layoutOptions; } set layoutOptions(value) { console.log(`set layoutOptions: ${value?.name}`); this._layoutOptions = value; } layoutOptionsChange = new EventEmitter(); layoutOptionsList = [ new BreadthFirstLayoutOptionsImpl(), new CoseLayoutOptionsImpl(), new DagreLayoutOptionsImpl(), new CircleLayoutOptionsImpl(), new ConcentricLayoutOptionsImpl(), new GridLayoutOptionsImpl(), new PresetLayoutOptionsImpl(), new RandomLayoutOptionsImpl(), new NullLayoutOptionsImpl(), ]; formInfo; constructor() { } ngOnInit() { this.formInfo = CytoscapeLayoutToolComponent.createLayoutFormInfo(); let layoutOptionsSelect = this.layoutOptionsList[5]; console.log('setting the initial selected layout, default: ', layoutOptionsSelect.name); if (this.layoutOptions) { console.log(`setting the initial selected layout based on input/output layout ${JSON.stringify(this.layoutOptions)}`); this.addOrReplaceInLayoutOptionsList(this.layoutOptions); } console.log('Initializing this.selectedLayoutInfo with layoutOptionsSelect ', JSON.stringify(layoutOptionsSelect)); } ngOnChanges(changes) { console.log('ngOnChanges layout changes:', JSON.stringify(changes)); if (changes['layoutOptions']) { } } onLayoutModelChange() { console.log('Layout model change: ', JSON.stringify(this.layoutOptions)); this.changed = true; } onFormModelChange() { console.log('onFormModelChange'); this.changed = true; } onApplyLayout() { this.changed = false; this.layoutOptionsChange.emit(this.layoutOptions); } addOrReplaceInLayoutOptionsList(layoutOptions) { let matchingOptions = this.layoutOptionsList.find((selectOption) => selectOption.name === layoutOptions.name); if (matchingOptions) { console.log('got matching layoutOptions: ', JSON.stringify(matchingOptions)); this.layoutOptionsList.splice(this.layoutOptionsList.indexOf(matchingOptions), 1, layoutOptions); } else { console.info(`Did you pass a new kind of layout? The layout name ${name} was not found, adding a new one to the top of the list.`); this.layoutOptionsList.unshift(layoutOptions); } } static createLayoutFormInfo() { let fit = new FieldInfo('Fit', 'fit', 'boolean', 'Whether to fit to viewport'); let padding = new FieldInfo('Padding', 'padding', 'number', 'When fit to viewport, padding inside the viewport.'); let fitFieldset = new FieldsetInfo('Fit', [fit, padding], ['fit']); const zoom = new FieldInfo('Zoom', 'zoom', 'number', 'the zoom level to set (likely want fit = false if set)'); const pan = new FieldInfo('Pan', 'pan', 'number', 'the pan level to set (likely want fit = false if set)'); const animate = new FieldInfo('Animate', 'animate', 'boolean', 'whether to transition the node positions'); const animationDuration = new FieldInfo('Animation Duration', 'animationDuration', 'number', 'duration of animation in ms if enabled'); const animationEasing = new FieldInfo('Animation Easing', 'animationEasing', 'number', 'easing of animation if enabled'); let animationFieldset = new FieldsetInfo('Animation', [zoom, pan, animate, animationDuration, animationEasing], ['animate']); let avoidOverlap = new FieldInfo('Avoid Overlap', 'avoidOverlap', 'boolean', 'prevents node overlap, may overflow boundingBox if not enough space'); let spacingFactor = new FieldInfo('Spacing Factor', 'spacingFactor', 'number', 'Applies a multiplicative factor (>0) to expand or compress the overall area that the nodes take up'); let nodeDimensionsIncludeLabels = new FieldInfo('Node Dimensions Include Labels', 'nodeDimensionsIncludeLabels', 'boolean', 'Excludes the label when calculating node bounding boxes for the layout algorithm'); let shapedFieldset = new FieldsetInfo('Shaped', [avoidOverlap, spacingFactor, nodeDimensionsIncludeLabels], ['avoidOverlap']); let directed = new FieldInfo('Directed', 'breadthFirst', 'boolean', 'whether the tree is breadthFirst downwards (or edges can point in any direction if false)'); let circle = new FieldInfo('Circle', 'circle', 'boolean', 'put depths in concentric circles if true, put depths top down if false'); let maximalAdjustments = new FieldInfo('Maximal Adjustments', 'maximalAdjustments', 'number', 'how many times to try to position the nodes in a maximal way (i.e. no backtracking)'); let maximal = new FieldInfo('Maximal', 'maximal', 'boolean', 'whether to shift nodes down their natural BFS depths in order to avoid upwards edges (DAGS only)'); let grid = new FieldInfo('Grid', 'grid', 'boolean', 'whether to shift nodes down their natural BFS depths in order to avoid upwards edges (DAGS only)'); let roots = new FieldInfo('Roots', 'roots', 'string', 'the roots of the trees'); let breadthFirstFieldset = new FieldsetInfo('Breadth First', [directed, circle, maximalAdjustments, maximal, grid, roots], ['breadthFirst']); let nodeSep = new FieldInfo('Node Separation', 'nodeSep', 'number', 'the separation between adjacent nodes in the same rank'); let edgeSep = new FieldInfo('Edge Separation', 'edgeSep', 'number', 'the separation between adjacent edges in the same rank'); let rankSep = new FieldInfo('Rank Separation', 'rankSep', 'number', 'the separation between each rank in the layout'); let ranker = new FieldInfo('Ranker', 'ranker', 'options', 'Type of algorithm to assign a rank to each node in the input graph.'); ranker.options = [ { name: '', label: '' }, { name: 'network-simplex', label: 'network-simplex' }, { name: 'tight-tree', label: 'tight-tree' }, { name: 'longest-path', label: 'longest-path' }, ]; let dagreFieldset = new FieldsetInfo('Dagre', [nodeSep, edgeSep, rankSep, ranker], ['nodeSep']); let animationThreshold = new FieldInfo('Animation Threshold', 'animationThreshold', 'number', 'The layout animates only after this many milliseconds when animate is true (prevents flashing on fast runs)'); let refresh = new FieldInfo('Refresh', 'refresh', 'number', 'Number of iterations between consecutive screen positions update'); let randomize = new FieldInfo('Randomize', 'randomize', 'boolean', 'Randomize the initial positions of the nodes (true) or use existing positions (false)'); let componentSpacing = new FieldInfo('Component Spacing', 'componentSpacing', 'number', 'Extra spacing between components in non-compound graphs'); let nodeOverlap = new FieldInfo('Node Overlap', 'nodeOverlap', 'number', 'Node repulsion (overlapping) multiplier'); let nestingFactor = new FieldInfo('Nesting Factor', 'nestingFactor', 'number', 'Nesting factor (multiplier) to compute ideal edge length for nested edges'); let gravity = new FieldInfo('Gravity', 'gravity', 'number', 'Gravity force (constant)'); let numIter = new FieldInfo('Max Iterations', 'numIter', 'number', 'Maximum number of iterations to perform'); let initialTemp = new FieldInfo('Initial Temp', 'initialTemp', 'number', 'Initial temperature (maximum node displacement)'); let coolingFactor = new FieldInfo('Cooling Factor', 'coolingFactor', 'number', 'Cooling factor (how the temperature is reduced between consecutive iterations'); let minTemp = new FieldInfo('Min. Temp', 'minTemp', 'number', 'Lower temperature threshold (below this point the layout will end)'); let coseFieldset = new FieldsetInfo('COSE', [ animationThreshold, refresh, randomize, componentSpacing, nodeOverlap, nestingFactor, gravity, numIter, initialTemp, coolingFactor, minTemp, ], ['coolingFactor']); let avoidOverlapPadding = new FieldInfo('avoidOverlapPadding', 'avoidOverlapPadding', 'number', 'extra spacing around nodes when avoidOverlap: true'); let condense = new FieldInfo('condense', 'condense', 'boolean', 'uses all available space on false, uses minimal space on true'); let rows = new FieldInfo('Rows', 'rows', 'number', 'force num of rows in the grid'); let cols = new FieldInfo('Columns', 'cols', 'number', 'force num of columns in the grid'); let gridFieldset = new FieldsetInfo('Grid', [avoidOverlapPadding, condense, rows, cols], ['cols']); let radius = new FieldInfo('Radius', 'radius', 'number', 'the radius of the circle'); let startAngle = new FieldInfo('Start Angle', 'startAngle', 'number', 'where nodes start in radians (default:3 / 2 * Math.PI)'); let sweep = new FieldInfo('Sweep', 'sweep', 'number', 'how many radians should be between the first and last node (defaults to full circle)'); let clockwise = new FieldInfo('Clockwise', 'clockwise', 'number', 'whether the layout should go clockwise (true) or counterclockwise/anticlockwise (false)'); let circularFieldSet = new FieldsetInfo('Circular', [radius, startAngle, sweep, clockwise], ['clockwise']); let equidistant = new FieldInfo('Equidistant', 'equidistant', 'boolean', 'whether levels have an equal radial distance betwen them, may cause bounding box overflow'); let minNodeSpacing = new FieldInfo('Min. Node Spacing', 'minNodeSpacing', 'number', 'min spacing between outside of nodes (used for radius adjustment)'); let height = new FieldInfo('Height', 'height', 'number', ''); let width = new FieldInfo('Width', 'width', 'number', ''); let concentricFieldSet = new FieldsetInfo('Concentric', [equidistant, minNodeSpacing, startAngle, height, width], ['equidistant']); //boundingBox: undefined // constrain layout bounds; { x1, y1, x2, y2 } or { x1, y1, w, h } return new FormInfo('Layout', [ breadthFirstFieldset, coseFieldset, dagreFieldset, gridFieldset, circularFieldSet, concentricFieldSet, fitFieldset, animationFieldset, shapedFieldset, ], false); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.3", ngImport: i0, type: CytoscapeLayoutToolComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "17.3.3", type: CytoscapeLayoutToolComponent, selector: "cytoscape-layout-tool", inputs: { layoutOptions: "layoutOptions" }, outputs: { layoutOptionsChange: "layoutOptionsChange" }, viewQueries: [{ propertyName: "layoutForm", first: true, predicate: ["layoutForm"], descendants: true }], usesOnChanges: true, ngImport: i0, template: ` <div> <div style="display: flex;"> <div class="layout-header">Edit Layout</div> </div> <p-dropdown class="layout-dropdown" name="selectedLayoutInfo" [options]="layoutOptionsList" [(ngModel)]="layoutOptions" optionLabel="name" (ngModelChange)="onLayoutModelChange()" > ></p-dropdown > <button class="apply-button" pButton label="Apply" [disabled]="!changed" (click)="onApplyLayout()" ></button> </div> <cyng-fluid-form [model]="layoutOptions" [formInfo]="formInfo" (modelChange)="onFormModelChange()" ></cyng-fluid-form> `, isInline: true, styles: [":host{width:400px;height:2em}.layout-header{width:100%;height:20px}.layout-dropdown{padding-right:10px}input:disabled{background-color:#cccccc54}\n"], dependencies: [{ kind: "directive", type: i2$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i3.ButtonDirective, selector: "[pButton]", inputs: ["iconPos", "loadingIcon", "label", "icon", "loading"] }, { kind: "component", type: i4.Dropdown, selector: "p-dropdown", inputs: ["id", "scrollHeight", "filter", "name", "style", "panelStyle", "styleClass", "panelStyleClass", "readonly", "required", "editable", "appendTo", "tabindex", "placeholder", "filterPlaceholder", "filterLocale", "inputId", "dataKey", "filterBy", "filterFields", "autofocus", "resetFilterOnHide", "dropdownIcon", "optionLabel", "optionValue", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "autoDisplayFirst", "group", "showClear", "emptyFilterMessage", "emptyMessage", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "overlayOptions", "ariaFilterLabel", "ariaLabel", "ariaLabelledBy", "filterMatchMode", "maxlength", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "focusOnHover", "selectOnFocus", "autoOptionFocus", "autofocusFilter", "disabled", "itemSize", "autoZIndex", "baseZIndex", "showTransitionOptions", "hideTransitionOptions", "filterValue", "options"], outputs: ["onChange", "onFilter", "onFocus", "onB