org.caleydo.view.crossword.api.ui.CrosswordMultiElement.java Source code

Java tutorial

Introduction

Here is the source code for org.caleydo.view.crossword.api.ui.CrosswordMultiElement.java

Source

/*******************************************************************************
 * Caleydo - Visualization for Molecular Biology - http://caleydo.org
 * Copyright (c) The Caleydo Team. All rights reserved.
 * Licensed under the new BSD license, available at http://caleydo.org/license
 ******************************************************************************/
package org.caleydo.view.crossword.api.ui;

import static org.caleydo.core.data.collection.EDimension.DIMENSION;
import static org.caleydo.core.data.collection.EDimension.RECORD;
import static org.caleydo.view.crossword.api.ui.layout.EEdgeType.PARENT_CHILD;
import static org.caleydo.view.crossword.api.ui.layout.EEdgeType.SHARED;
import static org.caleydo.view.crossword.api.ui.layout.EEdgeType.SIBLING;
import gleem.linalg.Vec2f;
import gleem.linalg.Vec4f;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import org.caleydo.core.data.collection.EDimension;
import org.caleydo.core.data.collection.table.Table;
import org.caleydo.core.data.datadomain.ATableBasedDataDomain;
import org.caleydo.core.data.perspective.table.TablePerspective;
import org.caleydo.core.data.perspective.variable.Perspective;
import org.caleydo.core.data.virtualarray.group.Group;
import org.caleydo.core.data.virtualarray.group.GroupList;
import org.caleydo.core.id.IDType;
import org.caleydo.core.view.opengl.layout2.GLElement;
import org.caleydo.core.view.opengl.layout2.GLElementAccessor;
import org.caleydo.core.view.opengl.layout2.GLGraphics;
import org.caleydo.core.view.opengl.layout2.IGLElementContext;
import org.caleydo.core.view.opengl.layout2.IGLElementParent;
import org.caleydo.core.view.opengl.layout2.basic.ScrollingDecorator.IHasMinSize;
import org.caleydo.core.view.opengl.layout2.geom.Rect;
import org.caleydo.core.view.opengl.layout2.layout.IGLLayoutElement;
import org.caleydo.view.crossword.api.model.CenterRadius;
import org.caleydo.view.crossword.api.model.ConnectorStrategies;
import org.caleydo.view.crossword.api.model.PerspectiveMetaData;
import org.caleydo.view.crossword.api.model.TablePerspectiveMetaData;
import org.caleydo.view.crossword.api.model.TypedSet;
import org.caleydo.view.crossword.api.ui.layout.EEdgeType;
import org.caleydo.view.crossword.api.ui.layout.IGraphEdge;
import org.caleydo.view.crossword.api.ui.layout.IGraphVertex;
import org.caleydo.view.crossword.api.ui.layout.IVertexConnector;
import org.caleydo.view.crossword.internal.CrosswordView;
import org.caleydo.view.crossword.internal.ui.CrosswordBandLayer;
import org.caleydo.view.crossword.internal.ui.CrosswordElement;
import org.caleydo.view.crossword.internal.ui.CrosswordLayoutInfo;
import org.caleydo.view.crossword.internal.ui.layout.DefaultGraphLayout;
import org.caleydo.view.crossword.spi.config.ElementConfig;
import org.caleydo.view.crossword.spi.config.MultiConfig;
import org.caleydo.view.crossword.spi.model.IBandRenderer;
import org.caleydo.view.crossword.spi.model.IConnectorStrategy;
import org.caleydo.view.crossword.spi.ui.layout.IGraphLayout;
import org.caleydo.view.crossword.spi.ui.layout.IGraphLayout.GraphLayoutModel;
import org.jgrapht.UndirectedGraph;
import org.jgrapht.graph.DefaultEdge;
import org.jgrapht.graph.Pseudograph;

import com.google.common.base.Function;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Iterators;

/**
 * crossword root element
 *
 * @author Samuel Gratzl
 *
 */
public class CrosswordMultiElement extends GLElement
        implements IHasMinSize, IGLElementParent, Iterable<CrosswordElement> {

    /**
     * data structure for managing the elements
     */
    private final UndirectedGraph<GraphVertex, GraphEdge> graph = new Pseudograph<>(GraphEdge.class);
    /**
     * dedicated element/layer for the bands for better caching behavior
     */
    private final CrosswordBandLayer bands = new CrosswordBandLayer();

    private final MultiConfig config;

    private final IGraphLayout layout;

    /**
     * whether the bars of all element should be shown all the time or just on demand (hover)
     */
    private boolean alwaysShowHeader;

    /**
     * result of the layout algorithm
     */
    private GraphLayoutModel layoutInstance;

    /**
     *
     */
    public CrosswordMultiElement(MultiConfig config) {
        this(new DefaultGraphLayout(), config);
    }

    /**
     * @param layout
     */
    public CrosswordMultiElement(IGraphLayout layout, MultiConfig config) {
        this.layout = layout;
        this.config = config;
        GLElementAccessor.setParent(bands, this);
    }

    @Override
    public Vec2f getMinSize() {
        float x = 0;
        float y = 0;
        for (GLElement child : this) {
            Vec4f bounds = child.getBounds();
            x = Math.max(x, bounds.x() + bounds.z());
            y = Math.max(y, bounds.y() + bounds.w());
        }
        return new Vec2f(x, y);
    }

    /**
     * @return the alwaysShowHeader, see {@link #alwaysShowHeader}
     */
    public boolean isAlwaysShowHeader() {
        return alwaysShowHeader;
    }

    public void toggleAlwaysShowHeader() {
        this.alwaysShowHeader = !this.alwaysShowHeader;
        for (CrosswordElement elem : Iterables.filter(this, CrosswordElement.class))
            elem.relayout();
    }

    @Override
    public void layout(int deltaTimeMs) {
        super.layout(deltaTimeMs);
        bands.layout(deltaTimeMs);
        for (GraphVertex vertex : graph.vertexSet())
            vertex.asElement().layout(deltaTimeMs);
    }

    @Override
    public void relayout() {
        super.relayout();
        GLElementAccessor.relayoutDown(bands);
    }

    @Override
    protected final void layoutImpl(int deltaTimeMs) {
        super.layoutImpl(deltaTimeMs);
        Vec2f size = getSize();
        GLElementAccessor.asLayoutElement(bands).setBounds(0, 0, size.x(), size.y());

        this.layoutInstance = layout.doLayout(graph.vertexSet(), graph.edgeSet());
        relayoutParent(); // trigger update of the parent for min size changes
    }

    /**
     * @return
     */
    public List<? extends IBandRenderer> getBandRoutes() {
        return (layoutInstance.getRoutes());
    }

    @Override
    protected void takeDown() {
        GLElementAccessor.takeDown(bands);
        for (GraphVertex vertex : graph.vertexSet())
            GLElementAccessor.takeDown(vertex.asElement());
        super.takeDown();
    }

    @Override
    protected void init(IGLElementContext context) {
        super.init(context);
        GLElementAccessor.init(bands, context);
        for (GraphVertex vertex : graph.vertexSet())
            GLElementAccessor.init(vertex.asElement(), context);
    }

    private void setup(CrosswordElement child) {
        IGLElementParent ex = child.getParent();
        boolean doInit = ex == null;
        if (ex == this) {
            // internal move
            removeVertex(child);
        } else if (ex != null) {
            doInit = !ex.moved(child);
        }
        GLElementAccessor.setParent(child, this);
        if (doInit && context != null)
            GLElementAccessor.init(child, context);
    }

    /**
     * @param child
     */
    private boolean removeVertex(GLElement child) {
        GraphVertex vertex = toVertex(child);
        if (vertex == null)
            return false;
        return graph.removeVertex(vertex);
    }

    /**
     * @param crosswordElement
     */
    private void add(CrosswordElement child) {
        GraphVertex vertex = addImpl(child);
        createBands(vertex, ImmutableSet.of(child));
    }

    private GraphVertex addImpl(CrosswordElement child) {
        setup(child);
        final GraphVertex vertex = new GraphVertex(child);
        graph.addVertex(vertex);
        relayout();
        return vertex;
    }

    private void createBands(GraphVertex child, Set<CrosswordElement> ignores) {
        final TablePerspective tablePerspective = child.getTablePerspective();
        final IDType recIDType = tablePerspective.getRecordPerspective().getIdType();
        final IDType dimIDType = tablePerspective.getDimensionPerspective().getIdType();

        for (GraphVertex other : graph.vertexSet()) {
            if (ignores.contains(other.asElement()))
                continue;
            final TablePerspective tablePerspective2 = child.getTablePerspective();
            final IDType recIDType2 = tablePerspective2.getRecordPerspective().getIdType();
            final IDType dimIDType2 = tablePerspective2.getDimensionPerspective().getIdType();
            if (recIDType.resolvesTo(recIDType2))
                addEdge(child, other, SHARED, connect(RECORD), connect(RECORD));
            if (dimIDType.resolvesTo(recIDType2))
                addEdge(child, other, SHARED, connect(DIMENSION), connect(RECORD));
            if (recIDType.resolvesTo(dimIDType2))
                addEdge(child, other, SHARED, connect(RECORD), connect(DIMENSION));
            if (dimIDType.resolvesTo(dimIDType2))
                addEdge(child, other, SHARED, connect(DIMENSION), connect(DIMENSION));
        }
    }

    private static VertexConnector connect(EDimension type) {
        return connect(type, ConnectorStrategies.SHARED);
    }

    private static VertexConnector connect(EDimension type, IConnectorStrategy model) {
        return new VertexConnector(type, model);
    }

    private void addEdge(GraphVertex child, GraphVertex other, EEdgeType type, VertexConnector sourceConnector,
            VertexConnector targetConnector) {
        final GraphEdge edge = new GraphEdge(type, sourceConnector, targetConnector);
        graph.addEdge(child, other, edge);
        edge.update();
    }

    /**
     * @param crosswordElement
     * @param dimensionSubTablePerspectives
     */
    private void split(CrosswordElement base, boolean inDim) {
        TablePerspective table = base.getTablePerspective();
        final EDimension type = EDimension.get(inDim);
        final GraphVertex baseVertex = toVertex(base);
        final GroupList groups = (inDim ? table.getDimensionPerspective() : table.getRecordPerspective())
                .getVirtualArray().getGroupList();
        assert groups.size() > 1;

        List<CrosswordElement> children = toElements(base, inDim, baseVertex, table);

        // combine the elements that should be ignored
        final ImmutableSet<CrosswordElement> ignore = ImmutableSet.<CrosswordElement>builder().addAll(children)
                .add(base).build();

        int total = inDim ? table.getNrDimensions() : table.getNrRecords();

        List<GraphVertex> vertices = new ArrayList<>(ignore.size());

        for (int i = 0; i < children.size(); ++i) {
            CrosswordElement child = children.get(i);
            Group group = groups.get(i);
            GraphVertex vertex = addImpl(child);
            vertices.add(vertex);
            createBands(vertex, ignore);
            int startIndex = group.getStartIndex();
            float offset = startIndex / (float) total;
            addEdge(baseVertex, vertex, PARENT_CHILD, connect(type, ConnectorStrategies.createParent(offset)),
                    connect(type));
            // add parent edge
            for (int j = 0; j < i; ++j) {
                GraphVertex child2 = vertices.get(j);
                addEdge(vertex, child2, SIBLING, connect(type.opposite()), connect(type.opposite())); // add sibling
                // edge
            }
        }

        // update metadata flags
        TablePerspectiveMetaData metaData = base.getMetaData();
        (inDim ? metaData.getDimension() : metaData.getRecord()).setSplitted();
    }

    private List<CrosswordElement> toElements(CrosswordElement base, boolean inDim, final GraphVertex baseVertex,
            TablePerspective table) {
        final List<TablePerspective> datas = inDim ? table.getDimensionSubTablePerspectives()
                : table.getRecordSubTablePerspectives();

        List<CrosswordElement> children = new ArrayList<>(datas.size());

        final ElementConfig econfig = config.getSplittedElementConfig(baseVertex);

        TablePerspectiveMetaData metaData = new TablePerspectiveMetaData(inDim ? 0 : PerspectiveMetaData.FLAG_CHILD,
                inDim ? PerspectiveMetaData.FLAG_CHILD : 0);
        for (TablePerspective t : datas) {
            final CrosswordElement new_ = new CrosswordElement(t, metaData, econfig, base);
            children.add(new_);
        }
        return children;
    }

    /**
     * @param crosswordElement
     */
    public void splitDim(CrosswordElement child) {
        split(child, true);
    }

    public void splitRec(CrosswordElement child) {
        split(child, false);
    }

    @Override
    public boolean moved(GLElement child) {
        if (child instanceof CrosswordElement)
            removeVertex(child);
        relayout();
        return context != null;
    }

    private final Function<GraphVertex, CrosswordElement> toCrosswordElement = new Function<GraphVertex, CrosswordElement>() {
        @Override
        public CrosswordElement apply(GraphVertex input) {
            return input.asElement();
        }
    };

    @Override
    public Iterator<CrosswordElement> iterator() {
        return Iterators.transform(graph.vertexSet().iterator(), toCrosswordElement);
    }

    @Override
    protected void renderImpl(GLGraphics g, float w, float h) {
        super.renderImpl(g, w, h);
        bands.render(g);
        g.incZ();
        for (GraphVertex vertex : graph.vertexSet())
            vertex.asElement().render(g);
        g.decZ();
    }

    @Override
    protected void renderPickImpl(GLGraphics g, float w, float h) {
        super.renderPickImpl(g, w, h);
        bands.renderPick(g);
        g.incZ();
        for (GraphVertex vertex : graph.vertexSet())
            vertex.asElement().renderPick(g);
        g.decZ();
    }

    @Override
    protected boolean hasPickAbles() {
        return super.hasPickAbles() || !graph.vertexSet().isEmpty();
    }

    /**
     * @param r
     */
    public void remove(GLElement child) {
        if (removeVertex(child)) {
            GLElementAccessor.takeDown(child);
            relayout();
        }
    }

    /**
     * @param tablePerspective
     */
    public void add(TablePerspective tablePerspective) {
        add(new CrosswordElement(tablePerspective, new TablePerspectiveMetaData(0, 0),
                config.getDefaultElementConfig()));
    }

    public void addAll(Iterable<TablePerspective> tablePerspectives) {
        for (TablePerspective tablePerspective : tablePerspectives)
            add(tablePerspective);
    }

    /**
     * @param removed
     */
    public void removeAll(Collection<TablePerspective> removed) {
        if (removed.isEmpty())
            return;
        List<CrosswordElement> toRemove = new ArrayList<>();
        for (CrosswordElement elem : Iterables.filter(this, CrosswordElement.class)) {
            if (removed.contains(elem.getTablePerspective())
                    || removed.contains(elem.getTablePerspective().getParentTablePerspective()))
                toRemove.add(elem);
        }
        for (CrosswordElement r : toRemove)
            remove(r);
    }

    public Iterable<GraphEdge> getBands() {
        return graph.edgeSet();
    }

    /**
     * @param crosswordElement
     */
    public void onConnectionsChanged(CrosswordElement child) {
        for (GraphEdge edge : graph.edgesOf(toVertex(child)))
            edge.update();
    }

    private GraphVertex toVertex(GLElement child) {
        for (GraphVertex vertex : graph.vertexSet())
            if (vertex.asElement() == child)
                return vertex;
        return null;
    }

    public void changePerspective(CrosswordElement child, boolean isDim, Perspective new_) {
        TablePerspective old = child.getTablePerspective();
        ATableBasedDataDomain dataDomain = old.getDataDomain();

        new_ = dataDomain.convertForeignPerspective(new_);
        Table table = dataDomain.getTable();

        Perspective record = old.getRecordPerspective();
        Perspective dimension = old.getDimensionPerspective();
        if (isDim)
            dimension = new_;
        else
            record = new_;
        TablePerspective newT;
        if (!table.containsDimensionPerspective(dimension.getPerspectiveID())
                || !table.containsRecordPerspective(record.getPerspectiveID())) {
            newT = new TablePerspective(dataDomain, record, dimension);
            newT.setPrivate(true);
        } else
            newT = dataDomain.getTablePerspective(record.getPerspectiveID(), dimension.getPerspectiveID());
        child.setTablePerspective(newT);
        if (context instanceof CrosswordView) {
            ((CrosswordView) context).replaceTablePerspectiveInternally(old, newT);
        }
    }

    private class GraphVertex implements IGraphVertex {
        private final CrosswordElement element;

        public GraphVertex(CrosswordElement element) {
            super();
            this.element = element;
        }

        public CrosswordElement asElement() {
            return element;
        }

        @Override
        public TypedSet getIDs(EDimension type) {
            return element.getIDs(type);
        }

        public TablePerspective getTablePerspective() {
            return element.getTablePerspective();
        }

        private IGLLayoutElement asLayoutElement() {
            return GLElementAccessor.asLayoutElement(element);
        }

        private CrosswordLayoutInfo asLayoutInfo() {
            return element.getLayoutDataAs(CrosswordLayoutInfo.class, null);
        }

        @Override
        public Vec2f getLocation() {
            return asLayoutElement().getLocation();
        }

        @Override
        public Rect getBounds() {
            return asLayoutElement().getRectBounds();
        }

        @Override
        public Vec2f getSize() {
            CrosswordLayoutInfo info = asLayoutInfo();
            return info.getSize();
        }

        @Override
        public void setBounds(Vec2f location, Vec2f size) {
            asLayoutElement().setBounds(location.x(), location.y(), size.x(), size.y());
        }

        @Override
        public void move(float x, float y) {
            Vec2f l = getLocation();
            asLayoutElement().setLocation(l.x() + x, l.y() + y);
        }

        @Override
        public Set<GraphEdge> getEdges() {
            return graph.edgesOf(this);
        }

        @Override
        public boolean hasEdge(EEdgeType type) {
            return Iterables.any(getEdges(), type);
        }
    }

    private static class VertexConnector implements IVertexConnector {
        private final EDimension type;
        private final IConnectorStrategy strategy;
        private CenterRadius values;

        public VertexConnector(EDimension type, IConnectorStrategy strategy) {
            this.strategy = strategy;
            this.type = type;
        }

        @Override
        public EDimension getDimension() {
            return type;
        }

        @Override
        public float getCenter() {
            return values.getCenter();
        }

        @Override
        public float getRadius() {
            return values.getRadius();
        }

        public void update(TypedSet ids, TypedSet intersection) {
            values = strategy.update(ids, intersection);
        }
    }

    private static class GraphEdge extends DefaultEdge implements IGraphEdge {
        private static final long serialVersionUID = -92295569780679555L;
        private final EEdgeType type;
        private TypedSet intersection;
        private final VertexConnector sourceConnector;
        private final VertexConnector targetConnector;

        public GraphEdge(EEdgeType type, VertexConnector sourceConnector, VertexConnector targetConnector) {
            this.type = type;
            this.sourceConnector = sourceConnector;
            this.targetConnector = targetConnector;

        }

        @Override
        public GraphVertex getSource() {
            return (GraphVertex) super.getSource();
        }

        @Override
        public GraphVertex getTarget() {
            return (GraphVertex) super.getTarget();
        }

        @Override
        public TypedSet getIntersection() {
            return intersection;
        }

        @Override
        public VertexConnector getSourceConnector() {
            return sourceConnector;
        }

        @Override
        public VertexConnector getTargetConnector() {
            return targetConnector;
        }

        @Override
        public EEdgeType getType() {
            return type;
        }

        /**
         *
         */
        public void update() {
            GraphVertex s = getSource();
            GraphVertex t = getTarget();
            TypedSet sourceIDs = s.getIDs(sourceConnector.getDimension());
            TypedSet targetIDs = t.getIDs(targetConnector.getDimension());
            intersection = sourceIDs.intersect(targetIDs);
            sourceConnector.update(sourceIDs, intersection);
            targetConnector.update(targetIDs, intersection);
        }
    }
}