|  | Home · Examples

# Code for Elastic Nodes Example

    /****************************************************************************
    **
    **  (C) 1992-2008 Trolltech ASA. All rights reserved.
    **
    ** This file is part of Qt Jambi.
    **
    ** ** This file may be used under the terms of the GNU General Public
    ** License version 2.0 as published by the Free Software Foundation
    ** and appearing in the file LICENSE.GPL included in the packaging of
    ** this file.  Please review the following information to ensure GNU
    ** General Public Licensing requirements will be met:
    ** http://www.trolltech.com/products/qt/opensource.html
    **
    ** If you are unsure which license is appropriate for your use, please
    ** review the following information:
    ** http://www.trolltech.com/products/qt/licensing.html or contact the
    ** sales department at sales@trolltech.com.

    **
    ** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
    ** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
    **
    ****************************************************************************/

    package com.trolltech.examples;

    import java.util.Vector;

    import com.trolltech.qt.core.*;
    import com.trolltech.qt.gui.*;

    @QtJambiExample(name = "Elastic Nodes")
    public class ElasticNodes extends QGraphicsView {

        public static void main(String args[]) {
            QApplication.initialize(args);
            ElasticNodes elasticNodes = new ElasticNodes();
            elasticNodes.show();
            QApplication.exec();
        }

        private int timerId;
        private Node centerNode;
        private Vector<Node> nodes = new Vector<Node>();

        private static final QBrush BRUSH_DARK_GRAY = new QBrush(QColor.darkGray);
        private static final QPen QPEN_EDGE = new QPen(QColor.black, 1, Qt.PenStyle.SolidLine, Qt.PenCapStyle.RoundCap, Qt.PenJoinStyle.RoundJoin);
        private static final QPen QPEN_BLACK = new QPen(QColor.black, 0);

        private static QRadialGradient GRADIENT_SUNKEN;
        private static QRadialGradient GRADIENT_NORMAL;

        private static QPainterPath NODE_SHAPE;
        static{
            NODE_SHAPE = new QPainterPath();
            NODE_SHAPE.addEllipse(-10, -10, 20, 20);

            GRADIENT_SUNKEN = new QRadialGradient(-3, -3, 10);
            GRADIENT_SUNKEN.setCenter(3, 3);
            GRADIENT_SUNKEN.setFocalPoint(3, 3);
            GRADIENT_SUNKEN.setColorAt(1, new QColor(QColor.yellow).lighter(120));
            GRADIENT_SUNKEN.setColorAt(0, new QColor(QColor.darkYellow).lighter(120));

            GRADIENT_NORMAL = new QRadialGradient(-3, -3, 10);
            GRADIENT_NORMAL.setColorAt(0, QColor.yellow);
            GRADIENT_NORMAL.setColorAt(1, QColor.darkYellow);
        }

        public ElasticNodes() {
            QGraphicsScene scene = new QGraphicsScene(this);
            scene.setItemIndexMethod(QGraphicsScene.ItemIndexMethod.NoIndex);
            scene.setSceneRect(-200, -200, 400, 400);
            setScene(scene);

            setCacheMode(new QGraphicsView.CacheMode(QGraphicsView.CacheModeFlag.CacheBackground));

            setRenderHint(QPainter.RenderHint.Antialiasing);
            setTransformationAnchor(QGraphicsView.ViewportAnchor.AnchorUnderMouse);
            setResizeAnchor(QGraphicsView.ViewportAnchor.AnchorViewCenter);

            Node node1 = new Node(this);
            Node node2 = new Node(this);
            Node node3 = new Node(this);
            Node node4 = new Node(this);
            centerNode = new Node(this);
            Node node6 = new Node(this);
            Node node7 = new Node(this);
            Node node8 = new Node(this);
            Node node9 = new Node(this);
            scene.addItem(node1);
            scene.addItem(node2);
            scene.addItem(node3);
            scene.addItem(node4);
            scene.addItem(centerNode);
            scene.addItem(node6);
            scene.addItem(node7);
            scene.addItem(node8);
            scene.addItem(node9);
            scene.addItem(new Edge(node1, node2));
            scene.addItem(new Edge(node2, node3));
            scene.addItem(new Edge(node2, centerNode));
            scene.addItem(new Edge(node3, node6));
            scene.addItem(new Edge(node4, node1));
            scene.addItem(new Edge(node4, centerNode));
            scene.addItem(new Edge(centerNode, node6));
            scene.addItem(new Edge(centerNode, node8));
            scene.addItem(new Edge(node6, node9));
            scene.addItem(new Edge(node7, node4));
            scene.addItem(new Edge(node8, node7));
            scene.addItem(new Edge(node9, node8));

            node1.setPos(-50, -50);
            node2.setPos(0, -50);
            node3.setPos(50, -50);
            node4.setPos(-50, 0);
            centerNode.setPos(0, 0);
            node6.setPos(50, 0);
            node7.setPos(-50, 50);
            node8.setPos(0, 50);
            node9.setPos(50, 50);

            scale(0.8, 0.8);

            for (QGraphicsItemInterface item : scene().items()) {
                if (item instanceof Node)
                    nodes.add((Node) item);
            }

            setMinimumSize(400, 400);
            setWindowTitle(tr("Elastic Nodes"));
            setWindowIcon(new QIcon("classpath:com/trolltech/images/qt-logo.png"));
        }

        private void itemMoved() {
            if (timerId == 0)
                timerId = startTimer(1000 / 25);
        }

        @Override
        protected void keyPressEvent(QKeyEvent event) {
            Qt.Key key = Qt.Key.resolve(event.key());
            switch (key) {
            case Key_Up:
                centerNode.moveBy(0, -20);
                break;
            case Key_Down:
                centerNode.moveBy(0, 20);
                break;
            case Key_Left:
                centerNode.moveBy(-20, 0);
                break;
            case Key_Right:
                centerNode.moveBy(20, 0);
                break;
            case Key_Plus:
                scaleView(1.2);
                break;
            case Key_Minus:
                scaleView(1 / 1.2);
                break;
            case Key_Space:
            case Key_Enter:
                for (QGraphicsItemInterface item : scene().items()) {
                    if (item instanceof Node)
                        item.setPos(-150 + Math.random() * 300, -150 + Math.random() * 300);
                }
                break;
            default:
                super.keyPressEvent(event);
            }
        }

        @Override
        protected void timerEvent(QTimerEvent event) {
           for (Node node : nodes)
                node.calculateForces();

            boolean itemsMoved = false;
            for (Node node : nodes) {
                if (node.advance())
                    itemsMoved = true;
            }

            if (!itemsMoved) {
                killTimer(timerId);
                timerId = 0;
            }
        }

        @Override
        protected void wheelEvent(QWheelEvent event) {
            scaleView(Math.pow(2, -event.delta() / 240.0));
        }

        @Override
        protected void drawBackground(QPainter painter, QRectF rect) {
            // Shadow
            QRectF sceneRect = this.sceneRect();
            QRectF rightShadow = new QRectF(sceneRect.right(), sceneRect.top() + 5, 5, sceneRect.height());
            QRectF bottomShadow = new QRectF(sceneRect.left() + 5, sceneRect.bottom(), sceneRect.width(), 5);
            if (rightShadow.intersects(rect) || rightShadow.contains(rect))
                painter.fillRect(rightShadow, BRUSH_DARK_GRAY);
            if (bottomShadow.intersects(rect) || bottomShadow.contains(rect))
                painter.fillRect(bottomShadow, BRUSH_DARK_GRAY);

            // Fill
            QLinearGradient gradient = new QLinearGradient(sceneRect.topLeft(), sceneRect.bottomRight());
            gradient.setColorAt(0, QColor.white);
            gradient.setColorAt(1, QColor.lightGray);
            painter.fillRect(rect.intersected(sceneRect), new QBrush(gradient));
            painter.setBrush(QBrush.NoBrush);
            painter.drawRect(sceneRect);

            // Text
            QRectF textRect = new QRectF(sceneRect.left() + 4, sceneRect.top() + 4, sceneRect.width() - 4, sceneRect.height() - 4);
            String message = tr("Click and drag the nodes around, and zoom with the mouse wheel or the '+' and '-' keys");

            QFont font = painter.font();
            font.setBold(true);
            font.setPointSize(14);
            painter.setFont(font);
            painter.setPen(QColor.lightGray);
            painter.drawText(textRect.translated(2, 2), message);
            painter.setPen(QColor.black);
            painter.drawText(textRect, message);
        }

        private void scaleView(double scaleFactor) {
            QMatrix m = matrix();
            m.scale(scaleFactor, scaleFactor);
            double factor = m.mapRect(new QRectF(0, 0, 1, 1)).width();
            if (factor < 0.07 || factor > 100)
                return;

            scale(scaleFactor, scaleFactor);
        }

        public class Node extends QGraphicsItem {

            private Vector<Edge> edgeList = new Vector<Edge>();
            private QPointF newPos;
            private ElasticNodes graph;
            private double adjust = 2;
            private QRectF boundingRect = new QRectF(-10 - adjust, -10 - adjust, 23 + adjust, 23 + adjust);

            Node(ElasticNodes graphWidget) {
                graph = graphWidget;
                setFlag(QGraphicsItem.GraphicsItemFlag.ItemIsMovable);
                setZValue(1);
                newPos = pos();
            }

            private void addEdge(Edge edge) {
                edgeList.add(edge);
                edge.adjust();
            }

            private void calculateForces() {
                if (scene() == null || scene().mouseGrabberItem() == this) {
                    newPos = pos();
                    return;
                }
                // Sum up all forces pushing this item away
                double xvel = 0;
                double yvel = 0;
                for (Node node : nodes) {
                    QPointF deltaPoint = mapFromItem(node, 0, 0);
                    double dx = -deltaPoint.x();
                    double dy = -deltaPoint.y();
                    double l = 2.0 * (dx * dx + dy * dy);
                    if (l > 0) {
                        xvel += (dx * 150.0) / l;
                        yvel += (dy * 150.0) / l;
                    }
                }

                // Now subtract all forces pulling items together
                double weight = (edgeList.size() + 1) * 10;
                for (Edge edge : edgeList) {
                    QPointF pos;
                    if (edge.sourceNode() == this)
                        pos = mapFromItem(edge.destNode(), 0, 0);
                    else
                        pos = mapFromItem(edge.sourceNode(), 0, 0);
                    xvel += pos.x() / weight;
                    yvel += pos.y() / weight;
                }

                if (Math.abs(xvel) < 0.1 && Math.abs(yvel) < 0.1)
                    xvel = yvel = 0;

                QRectF sceneRect = scene().sceneRect();
                newPos.setX(Math.min(Math.max(newPos.x() + xvel, sceneRect.left() + 10), sceneRect.right() - 10));
                newPos.setY(Math.min(Math.max(newPos.y() + yvel, sceneRect.top() + 10), sceneRect.bottom() - 10));
            }

            private boolean advance() {
                if (newPos == pos())
                    return false;

                setPos(newPos);
                return true;
            }

            @Override
            public QRectF boundingRect() {
                return boundingRect;
            }

            @Override
            public QPainterPath shape() {
                return NODE_SHAPE;
            }

            @Override
            public void paint(QPainter painter, QStyleOptionGraphicsItem option, QWidget widget) {
                painter.setPen(Qt.PenStyle.NoPen);
                painter.setBrush(QColor.fromRgba(QColor.black.rgb() & 0x7fffffff));
                painter.drawEllipse(-7, -7, 20, 20);

                if ((option.state().isSet(QStyle.StateFlag.State_Sunken))) {
                    painter.setBrush(GRADIENT_SUNKEN);
                } else {
                    painter.setBrush(GRADIENT_NORMAL);
                }

                painter.setPen(QPEN_BLACK);
                painter.drawEllipse(-10, -10, 20, 20);
            }

            @Override
            public Object itemChange(GraphicsItemChange change, Object value) {
                switch (change) {
                case ItemPositionChange:
                    for (Edge edge : edgeList)
                        edge.adjust();
                    graph.itemMoved();
                    break;
                default:
                    break;
                }

                return super.itemChange(change, value);
            }

            @Override
            public void mousePressEvent(QGraphicsSceneMouseEvent event) {
                update();
                super.mousePressEvent(event);
            }

            @Override
            public void mouseReleaseEvent(QGraphicsSceneMouseEvent event) {
                update();
                super.mouseReleaseEvent(event);
            }
        }

        public class Edge extends QGraphicsItem {
            private Node source;
            private Node dest;

            private QPointF sourcePoint = new QPointF();
            private QPointF destPoint = new QPointF();
            private double arrowSize = 10;
            private double penWidth = 1;
            private double extra = (penWidth + arrowSize) / 2.0;

            private QRectF boundingRect = new QRectF();

            QPointF sourceArrowP1 = new QPointF();
            QPointF sourceArrowP2 = new QPointF();
            QPointF destArrowP1 = new QPointF();
            QPointF destArrowP2 = new QPointF();

            QPolygonF pol1 = new QPolygonF();
            QPolygonF pol2 = new QPolygonF();

            public Edge(Node sourceNode, Node destNode) {
                // setAcceptedMouseButtons(LeftButton);
                source = sourceNode;
                dest = destNode;
                source.addEdge(this);
                dest.addEdge(this);
                adjust();
            }

            private Node sourceNode() {
                return source;
            }

            private Node destNode() {
                return dest;
            }

            private void adjust() {
                double dx = source.pos().x()-dest.pos().x();
                double dy = source.pos().y()-dest.pos().y();

                double length = Math.sqrt(dx*dx+dy*dy);
                if (length == 0.0) return;

                double paddingX = dx/length*10;
                double paddingY = dy/length*10;

                removeFromIndex();
                sourcePoint.setX(source.pos().x() - paddingX);
                sourcePoint.setY(source.pos().y() - paddingY);

                destPoint.setX(dest.pos().x() + paddingX);
                destPoint.setY(dest.pos().y() + paddingY);

                boundingRect.setBottomLeft(source.pos());
                boundingRect.setTopRight(dest.pos());

                boundingRect = boundingRect.normalized();

                boundingRect.adjust(-extra, -extra, extra, extra);
                addToIndex();

            }

            @Override
            public QRectF boundingRect() {
                return boundingRect;
            }

            @Override
            public void paint(QPainter painter, QStyleOptionGraphicsItem option, QWidget widget) {

                if (source == null || dest == null)
                    return;

                // Draw the line itself
                QLineF line = new QLineF(sourcePoint, destPoint);

                painter.setPen(QPEN_EDGE);
                painter.drawLine(line);

                // Draw the arrows if there's enough room
                double angle;
                if (line.length() > 0)
                    angle = Math.acos(line.dx() / line.length());
                else
                    angle = 0;

                if (line.dy() >= 0)
                    angle = (Math.PI * 2) - angle;

                sourceArrowP1.setX(sourcePoint.x() + Math.sin(angle + Math.PI / 3) * arrowSize);
                sourceArrowP1.setY(sourcePoint.y() + Math.cos(angle + Math.PI / 3) * arrowSize);

                sourceArrowP2.setX(sourcePoint.x() + Math.sin(angle + Math.PI - Math.PI / 3) * arrowSize);
                sourceArrowP2.setY(sourcePoint.y() + Math.cos(angle + Math.PI - Math.PI / 3) * arrowSize);

                destArrowP1.setX(destPoint.x() + Math.sin(angle - Math.PI / 3) * arrowSize);
                destArrowP1.setY(destPoint.y() + Math.cos(angle - Math.PI / 3) * arrowSize);

                destArrowP2.setX(destPoint.x() + Math.sin(angle - Math.PI + Math.PI / 3) * arrowSize);
                destArrowP2.setY(destPoint.y() + Math.cos(angle - Math.PI + Math.PI / 3) * arrowSize);

                pol1.clear();
                pol2.clear();

                pol1.append(line.p1());
                pol1.append(sourceArrowP1);
                pol1.append(sourceArrowP2);

                pol2.append(line.p2());
                pol2.append(destArrowP1);
                pol2.append(destArrowP2);

                painter.setBrush(QColor.black);
                painter.drawPolygon(pol1);
                painter.drawPolygon(pol2);
            }
        }
    }