All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.teamapps.ux.component.charting.tree.TreeGraph Maven / Gradle / Ivy

There is a newer version: 0.9.194
Show newest version
/*-
 * ========================LICENSE_START=================================
 * TeamApps
 * ---
 * Copyright (C) 2014 - 2023 TeamApps.org
 * ---
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * =========================LICENSE_END==================================
 */
package org.teamapps.ux.component.charting.tree;

import org.teamapps.data.extract.BeanPropertyExtractor;
import org.teamapps.data.extract.PropertyExtractor;
import org.teamapps.data.extract.PropertyProvider;
import org.teamapps.dto.UiBaseTreeGraphNode;
import org.teamapps.dto.UiClientRecord;
import org.teamapps.dto.UiEvent;
import org.teamapps.dto.UiTreeGraph;
import org.teamapps.dto.UiTreeGraphNode;
import org.teamapps.event.Event;
import org.teamapps.ux.component.AbstractComponent;
import org.teamapps.ux.component.template.Template;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

public class TreeGraph extends AbstractComponent {

	public final Event> onNodeClicked = new Event<>();
	public final Event> onNodeExpandedOrCollapsed = new Event<>();
	public final Event> onParentExpandedOrCollapsed = new Event<>();
	public final Event> onSideListExpandedOrCollapsed = new Event<>();

	private float zoomFactor;
	private boolean compact = false;
	private int verticalLayerGap = 36;
	private int horizontalSiblingGap = 20;
	private int horizontalNonSignlingGap = 36;
	private int sideListIndent = 20;
	private int sideListVerticalGap = 20;

	private final LinkedHashMap> nodesById = new LinkedHashMap<>();
	private PropertyProvider propertyProvider = new BeanPropertyExtractor<>();

	public TreeGraph() {
	}

	@Override
	public UiTreeGraph createUiComponent() {
		UiTreeGraph ui = new UiTreeGraph();
		mapAbstractUiComponentProperties(ui);
		ui.setNodes(createUiNodes(nodesById.values()));
		ui.setZoomFactor(zoomFactor);
		ui.setCompact(compact);
		ui.setVerticalLayerGap(verticalLayerGap);
		ui.setSideListIndent(sideListIndent);
		ui.setSideListVerticalGap(sideListVerticalGap);
		ui.setHorizontalSiblingGap(horizontalSiblingGap);
		ui.setHorizontalNonSignlingGap(horizontalNonSignlingGap);
		return ui;
	}

	private List createUiNodes(Collection> nodes) {
		return nodes.stream()
				.map(this::createUiNode)
				.collect(Collectors.toList());
	}

	private UiTreeGraphNode createUiNode(TreeGraphNode node) {
		UiTreeGraphNode uiNode = new UiTreeGraphNode(node.getId(), node.getWidth(), node.getHeight());
		mapBaseTreeGraphNodeAttributes(node, uiNode);
		uiNode.setParentId(node.getParent() != null ? node.getParent().getId() : null);
		uiNode.setParentExpandable(node.isParentExpandable());
		uiNode.setParentExpanded(node.isParentExpanded());
		uiNode.setExpanded(node.isExpanded());
		uiNode.setHasLazyChildren(node.isHasLazyChildren());
		uiNode.setSideListNodes(node.getSideListNodes() != null ? node.getSideListNodes().stream().map(this::createBaseUiNode).collect(Collectors.toList()) : null);
		uiNode.setSideListExpanded(node.isSideListExpanded());
		return uiNode;
	}

	private UiBaseTreeGraphNode createBaseUiNode(BaseTreeGraphNode node) {
		UiBaseTreeGraphNode uiNode = new UiBaseTreeGraphNode(node.getId(), node.getWidth(), node.getHeight());
		mapBaseTreeGraphNodeAttributes(node, uiNode);
		return uiNode;
	}

	private void mapBaseTreeGraphNodeAttributes(BaseTreeGraphNode node, UiBaseTreeGraphNode uiNode) {
		uiNode.setBackgroundColor(node.getBackgroundColor() != null ? node.getBackgroundColor().toHtmlColorString() : null);
		uiNode.setBorderColor(node.getBorderColor() != null ? node.getBorderColor().toHtmlColorString() : null);
		uiNode.setBorderWidth(node.getBorderWidth());
		uiNode.setBorderRadius(node.getBorderRadius());
		uiNode.setImage(node.getImage() != null ? node.getImage().createUiTreeGraphNodeImage() : null);
		uiNode.setIcon(node.getIcon() != null ? node.getIcon().createUiTreeGraphNodeIcon() : null);
		uiNode.setTemplate(node.getTemplate() != null ? node.getTemplate().createUiTemplate() : null);
		uiNode.setRecord(node.getRecord() != null ? createUiRecord(node.getRecord(), node.getTemplate()) : null);
		uiNode.setConnectorLineColor(node.getConnectorLineColor() != null ? node.getConnectorLineColor().toHtmlColorString() : null);
		uiNode.setConnectorLineWidth(node.getConnectorLineWidth());
		uiNode.setDashArray(node.getDashArray());
	}

	private UiClientRecord createUiRecord(RECORD record, Template template) {
		UiClientRecord uiClientRecord = new UiClientRecord();
		uiClientRecord.setValues(propertyProvider.getValues(record, template.getPropertyNames()));
		return uiClientRecord;
	}

	public void setZoomFactor(float zoomFactor) {
		this.zoomFactor = zoomFactor;
		queueCommandIfRendered(() -> new UiTreeGraph.SetZoomFactorCommand(getId(), zoomFactor));
	}

	public void setNodes(List> nodes) {
		this.nodesById.clear();
		nodes.forEach(n -> nodesById.put(n.getId(), n));
		queueCommandIfRendered(() -> new UiTreeGraph.SetNodesCommand(getId(), createUiNodes(nodes)));
	}

	public void addNode(TreeGraphNode node) {
		nodesById.put(node.getId(), node);
		queueCommandIfRendered(() -> new UiTreeGraph.AddNodeCommand(getId(), createUiNode(node)));
	}

	public void addNodes(List> nodes) {
		nodes.forEach(n -> nodesById.put(n.getId(), n));
		update();
	}

	public void removeNode(TreeGraphNode node) {
		this.nodesById.remove(node.getId());
		queueCommandIfRendered(() -> new UiTreeGraph.RemoveNodeCommand(getId(), node.getId()));
	}

	public void updateNode(TreeGraphNode node) {
		nodesById.put(node.getId(), node);
		queueCommandIfRendered(() -> new UiTreeGraph.UpdateNodeCommand(getId(), createUiNode(node)));
	}

	@Override
	public void handleUiEvent(UiEvent event) {
		switch (event.getUiEventType()) {
			case UI_TREE_GRAPH_NODE_CLICKED: {
				UiTreeGraph.NodeClickedEvent e = (UiTreeGraph.NodeClickedEvent) event;
				TreeGraphNode node = this.nodesById.get(e.getNodeId());
				if (node != null) {
					onNodeClicked.fire(node);
				}
				break;
			}
			case UI_TREE_GRAPH_NODE_EXPANDED_OR_COLLAPSED: {
				UiTreeGraph.NodeExpandedOrCollapsedEvent e = (UiTreeGraph.NodeExpandedOrCollapsedEvent) event;
				TreeGraphNode node = this.nodesById.get(e.getNodeId());
				if (node != null) {
					node.setExpanded(e.getExpanded());
					onNodeExpandedOrCollapsed.fire(new NodeExpandedOrCollapsedEvent<>(node, e.getExpanded(), e.getLazyLoad()));
				}
				break;
			}
			case UI_TREE_GRAPH_PARENT_EXPANDED_OR_COLLAPSED: {
				UiTreeGraph.ParentExpandedOrCollapsedEvent e = (UiTreeGraph.ParentExpandedOrCollapsedEvent) event;
				TreeGraphNode node = this.nodesById.get(e.getNodeId());
				if (node != null) {
					node.setParentExpanded(e.getExpanded());
					onParentExpandedOrCollapsed.fire(new NodeExpandedOrCollapsedEvent<>(node, e.getExpanded(), e.getLazyLoad()));
				}
				break;
			}
			case UI_TREE_GRAPH_SIDE_LIST_EXPANDED_OR_COLLAPSED: {
				UiTreeGraph.SideListExpandedOrCollapsedEvent e = (UiTreeGraph.SideListExpandedOrCollapsedEvent) event;
				TreeGraphNode node = this.nodesById.get(e.getNodeId());
				if (node != null) {
					node.setSideListExpanded(e.getExpanded());
					onSideListExpandedOrCollapsed.fire(new SideListExpandedOrCollapsedEvent<>(node, e.getExpanded()));
				}
				break;
			}
		}
	}

	public boolean isCompact() {
		return compact;
	}

	public void setCompact(boolean compact) {
		this.compact = compact;
		update();
	}

	private void update() {
		queueCommandIfRendered(() -> new UiTreeGraph.UpdateCommand(getId(), createUiComponent()));
	}

	public void moveToRootNode() {
		queueCommandIfRendered(() -> new UiTreeGraph.MoveToRootNodeCommand(getId()));
	}

	public void moveToNode(TreeGraphNode node) {
		queueCommandIfRendered(() -> new UiTreeGraph.MoveToNodeCommand(getId(), node.getId()));
	}

	private Collection> getAllDescendants(TreeGraphNode node, boolean includeSelf) {
		// O(h^2) where h is the height of the tree.
		// So the worst case performance is O(n * n/2) for a totally linear tree.
		// Average case: O(n * log(n))
		// If used for a root node (see usages!!): O(n) due to optimization!

		Set> descendants = new HashSet<>();
		descendants.add(node);
		Set> nonDescendants = new HashSet<>(getAncestors(node, false)); // common case optimization!

		List> untaggedNodes = new ArrayList<>(nodesById.values());
		untaggedNodes.remove(node);

		boolean[] descendantsChanged = new boolean[1];
		boolean[] nonDescendantsChanged = new boolean[1];
		do {
			descendantsChanged[0] = false;
			nonDescendantsChanged[0] = false;
			untaggedNodes = untaggedNodes.stream()
					.filter(n -> {
						if (descendants.contains(n.getParent())) {
							descendants.add(n);
							descendantsChanged[0] = true;
							return false;
						} else if (nonDescendants.contains(n.getParent())) {
							nonDescendants.add(n);
							nonDescendantsChanged[0] = true;
							return false;
						} else {
							return true;
						}
					})
					.collect(Collectors.toList());
		} while (descendantsChanged[0] && nonDescendantsChanged[0]);

		descendants.addAll(untaggedNodes); // if nonDescendantsChanged[0] == false, all remaining nodes must be descendants!

		if (!includeSelf) {
			descendants.remove(node);
		}

		return descendants;
	}

	private List> getAncestors(TreeGraphNode node, boolean includeSelf) {
		ArrayList> ancestors = new ArrayList<>();
		if (includeSelf) {
			ancestors.add(node);
		}
		while (node.getParent() != null) {
			node = node.getParent();
			ancestors.add(node);
		}
		return ancestors;
	}

	public int getVerticalLayerGap() {
		return verticalLayerGap;
	}

	public void setVerticalLayerGap(int verticalLayerGap) {
		this.verticalLayerGap = verticalLayerGap;
		this.update();
	}

	public int getSideListIndent() {
		return sideListIndent;
	}

	public void setSideListIndent(int sideListIndent) {
		this.sideListIndent = sideListIndent;
		this.update();
	}

	public int getSideListVerticalGap() {
		return sideListVerticalGap;
	}

	public void setSideListVerticalGap(int sideListVerticalGap) {
		this.sideListVerticalGap = sideListVerticalGap;
		this.update();
	}

	public int getHorizontalSiblingGap() {
		return horizontalSiblingGap;
	}

	public void setHorizontalSiblingGap(int horizontalSiblingGap) {
		this.horizontalSiblingGap = horizontalSiblingGap;
		this.update();
	}

	public int getHorizontalNonSignlingGap() {
		return horizontalNonSignlingGap;
	}

	public void setHorizontalNonSignlingGap(int horizontalNonSignlingGap) {
		this.horizontalNonSignlingGap = horizontalNonSignlingGap;
		this.update();
	}

	public PropertyProvider getPropertyProvider() {
		return propertyProvider;
	}

	public void setPropertyProvider(PropertyProvider propertyProvider) {
		this.propertyProvider = propertyProvider;
	}

	public void setPropertyExtractor(PropertyExtractor propertyExtractor) {
		this.setPropertyProvider(propertyExtractor);
	}
}


















































© 2015 - 2024 Weber Informatics LLC | Privacy Policy