de.iteratec.iteraplan.businesslogic.exchange.visio.VisioBubbleExport.java Source code

Java tutorial

Introduction

Here is the source code for de.iteratec.iteraplan.businesslogic.exchange.visio.VisioBubbleExport.java

Source

/*
 * iteraplan is an IT Governance web application developed by iteratec, GmbH
 * Copyright (C) 2004 - 2014 iteratec, GmbH
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU Affero General Public License version 3 as published by
 * the Free Software Foundation with the addition of the following permission
 * added to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED
 * WORK IN WHICH THE COPYRIGHT IS OWNED BY ITERATEC, ITERATEC DISCLAIMS THE
 * WARRANTY OF NON INFRINGEMENT  OF THIRD PARTY RIGHTS.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program; if not, see http://www.gnu.org/licenses or write to
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA 02110-1301 USA.
 *
 * You can contact iteratec GmbH headquarters at Inselkammerstr. 4
 * 82008 Munich - Unterhaching, Germany, or at email address info@iteratec.de.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU Affero General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU Affero General Public License
 * version 3, these Appropriate Legal Notices must retain the display of the
 * "iteraplan" logo. If the display of the logo is not reasonably
 * feasible for technical reasons, the Appropriate Legal Notices must display
 * the words "Powered by iteraplan".
 */
package de.iteratec.iteraplan.businesslogic.exchange.visio;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.apache.commons.lang.StringUtils;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;

import de.iteratec.iteraplan.businesslogic.exchange.common.Coordinates;
import de.iteratec.iteraplan.businesslogic.exchange.common.dimension.AttributeAdapter;
import de.iteratec.iteraplan.businesslogic.exchange.common.dimension.AttributeRangeAdapter;
import de.iteratec.iteraplan.businesslogic.exchange.common.dimension.Dimension;
import de.iteratec.iteraplan.businesslogic.exchange.common.dimension.DimensionAdapter;
import de.iteratec.iteraplan.businesslogic.exchange.common.dimension.PositionDimension;
import de.iteratec.iteraplan.businesslogic.exchange.common.dimension.SizeDimension;
import de.iteratec.iteraplan.businesslogic.exchange.common.portfolio.BubbleSpace;
import de.iteratec.iteraplan.businesslogic.exchange.common.portfolio.PositionInTile;
import de.iteratec.iteraplan.businesslogic.exchange.common.portfolio.Tile;
import de.iteratec.iteraplan.businesslogic.exchange.common.portfolio.Weight;
import de.iteratec.iteraplan.businesslogic.exchange.visio.legend.VisioAttributeLegend;
import de.iteratec.iteraplan.businesslogic.exchange.visio.legend.VisioNamesLegend;
import de.iteratec.iteraplan.businesslogic.exchange.visio.legend.VisioSizeLegend;
import de.iteratec.iteraplan.businesslogic.reports.query.options.GraphicalReporting.Dimension.DimensionOptionsBean;
import de.iteratec.iteraplan.businesslogic.reports.query.options.GraphicalReporting.Portfolio.IPortfolioOptions;
import de.iteratec.iteraplan.businesslogic.service.AttributeTypeService;
import de.iteratec.iteraplan.businesslogic.service.AttributeValueService;
import de.iteratec.iteraplan.common.Logger;
import de.iteratec.iteraplan.common.MessageAccess;
import de.iteratec.iteraplan.common.error.IteraplanErrorMessages;
import de.iteratec.iteraplan.common.error.IteraplanTechnicalException;
import de.iteratec.iteraplan.common.util.DateUtils;
import de.iteratec.iteraplan.common.util.InchConverter;
import de.iteratec.iteraplan.model.BuildingBlock;
import de.iteratec.iteraplan.model.TypeOfBuildingBlock;
import de.iteratec.iteraplan.model.attribute.AttributeType;
import de.iteratec.iteraplan.model.attribute.NumberAT;
import de.iteratec.visio.model.Document;
import de.iteratec.visio.model.Shape;
import de.iteratec.visio.model.exceptions.MasterNotFoundException;

/**
 * Contains the builder algorithm to generate the Visio portfolio diagram. This class does not use
 * the gxl2visio functionality, but directly uses the Visio element wrappers of the gxl2visio
 * library to generate a {@link Document}.
 */
@SuppressWarnings("PMD.TooManyMethods")
public class VisioBubbleExport extends VisioDimensionExport {

    private static final String VISIO_TEMPLATE_FILE = "/VisioBubbleTemplate.vdx";
    private static final String VISIO_DIAGRAM_TITLE = "iteraplan portfolio";

    private static final String VISIO_PROPERTY_DIMENSION = "Dimension";

    private static final String VISIO_SHAPE_NAME_COSY = "CoordinateSystem";
    private static final String VISIO_SHAPE_NAME_XAXIS = "X-Axis";
    private static final String VISIO_SHAPE_NAME_YAXIS = "Y-Axis";
    private static final String VISIO_SHAPE_NAME_BUBBLE = "Bubble";
    private static final String VISIO_SHAPE_NAME_RECT = "Rectangle";

    private static final String VISIO_SHAPE_NAME_COLOR_BUBBLE = "Color-Index-Bubble";
    private static final String VISIO_SHAPE_NAME_SIZE_ENTITY = "SizeEntity";
    private static final String VISIO_SHAPE_NAME_DIMHEADER = "Legend-Dimension-Header";
    private static final String VISIO_SHAPE_NAME_DIMCONTENT = "Legend-Dimension-Content";
    private static final String VISIO_SHAPE_NAME_ATTRHEADER = "Legend-Attribute-Header";
    private static final String VISIO_SHAPE_NAME_ATTRCONTENT = "Legend-Attribute-Content";

    private static final String SHAPE_LINE = "Line";
    private static final String VALUE_LINESTYLE_SOLID = "solid";

    // the maximum and minimum values are the boundaries adequate to the coordinate system shape (in
    // inches)
    private static final double MINIMUM_X = 1.57480315; // 40mm
    private static final double MAXIMUM_X = 12.7952756; // 325mm
    private static final double MINIMUM_Y = 1.57480315; // 40mm
    private static final double MAXIMUM_Y = 12.7952756; // 325mm

    private static final double LEGEND_MARGIN_CM = 1.3;

    // the absolute distance of axis values to the real coordinate system (in inches)
    private static final double AXIS_VALUES_FROM_COSY_DISTANCE = 0.9;

    private static final double COSY_SIDE = MAXIMUM_X - MINIMUM_X;

    // Visio operates in radians and counterclockwise so this is 90 degrees clockwise
    private static final double VISIO_VERTICAL_ANGLE = -Math.PI * 1.5;
    private static final double VISIO_HORIZONTAL_ANGLE = 0;

    // Scaling attributes
    private double scaleFactorX = 1; // Default
    // X
    // scale
    // factor
    private double scaleFactorY = 1; // Default
    // Y
    // scale
    // factor
    // The offsets at which the graphic should begin, in relation to the (0,0) point
    private double offsetX;
    private double offsetY;
    // The initial borders of the frame that encapsulates all the objects to be drawn in the
    // coordinate system
    private double locMinX = Double.MAX_VALUE;
    private double locMinY = Double.MAX_VALUE;

    private double legendFrameBaseX;
    private double legendFrameWidth;
    private double namesLegendFrameMaxY;
    private double scaling = 1;

    private final List<BuildingBlock> buildingBlocks;

    private final IPortfolioOptions portfolioOptions;

    private BubbleSpace bubbleSpace;

    private static final Logger LOGGER = Logger.getIteraplanLogger(VisioBubbleExport.class);

    public VisioBubbleExport(List<? extends BuildingBlock> blocks, IPortfolioOptions portfolioOptions,
            AttributeTypeService attributeTypeService, AttributeValueService attributeValueService) {
        super(attributeTypeService, attributeValueService);
        this.buildingBlocks = new ArrayList<BuildingBlock>(blocks);
        this.portfolioOptions = portfolioOptions;
    }

    /*
     * (non-Javadoc) This method initiates the drawing of the diagram.
     */
    @Override
    public Document createDiagram() {

        // Prepare a Document and a Page for writing
        init(VISIO_TEMPLATE_FILE, VISIO_DIAGRAM_TITLE);

        // Names legend
        if (portfolioOptions.isUseNamesLegend()) {
            setVisioNamesLegend(new VisioNamesLegend(getTargetPage()));
        }

        // Initialise the dimensions (X,Y,Colour,Size)
        bubbleSpace = createBubbleSpace(portfolioOptions, buildingBlocks);
        revertYAxesForVisio();
        try {
            // create the coordinatesystem
            createCoordinateSystem();

            // create labels for the axes
            String xLabel = bubbleSpace.getXDimension().getName();
            String yLabel = bubbleSpace.getYDimension().getName();
            createCoordinateLabels(xLabel, yLabel);

            Shape title = createDiagramTitle(getTitle(portfolioOptions.getSelectedBbType()));
            setTitlePosAndSize(title, 1, getTargetPage().getHeight(), null);

            createGeneratedInformation(getTargetPage().getWidth());
            createLogos(0, 0, getTargetPage().getWidth(), getTargetPage().getHeight());

            // Create the textual legend
            Shape legend;
            if (isOneDimensionNumber()) {
                legend = createLegend(true);
            } else {
                legend = createLegend(false);
            }

            createQueryInfo(legend);

            double colorLegendHeightInch = createColorLegend();

            // Add the bubbles to the diagram
            addResultNodes();

            double sizeLegendHeightInch = createSizeLegend();

            // Create the grid labels
            if (IPortfolioOptions.TYPE_XY.equals(portfolioOptions.getPortfolioType())) {
                createGridLabels(bubbleSpace.getXDimension(), false);
                createGridLabels(bubbleSpace.getYDimension(), true);
            }

            if (portfolioOptions.isUseNamesLegend()) {
                namesLegendFrameMaxY = namesLegendFrameMaxY - Math.max(colorLegendHeightInch, sizeLegendHeightInch)
                        - InchConverter.cmToInches(LEGEND_MARGIN_CM);

                double namesLegendBaseY = InchConverter.cmToInches(2 * LEGEND_MARGIN_CM);
                createNamesLegend(legendFrameBaseX, namesLegendBaseY, legendFrameWidth,
                        namesLegendFrameMaxY - namesLegendBaseY, portfolioOptions.isNakedExport(),
                        getTitle(portfolioOptions.getSelectedBbType()));
            }

        } catch (MasterNotFoundException e) {
            throw new IteraplanTechnicalException(IteraplanErrorMessages.INTERNAL_ERROR, e);
        }

        return getVisioDocument();
    }

    private Map<String, Object> addLabels(BuildingBlock block) {

        Map<String, Object> props = new HashMap<String, Object>();
        String label = "";
        int count = 1;

        Dimension<?> dim = bubbleSpace.getXDimension();
        String attrName = dim.getName();
        if (!StringUtils.isEmpty(attrName)) {
            label = attrName + "=" + block.getAttributeValue(attrName, getLocale());
            props.put(VISIO_PROPERTY_DIMENSION + count++, label);
        }

        dim = bubbleSpace.getYDimension();
        attrName = dim.getName();
        if (!StringUtils.isEmpty(attrName)) {
            label = attrName + "=" + block.getAttributeValue(attrName, getLocale());
            props.put(VISIO_PROPERTY_DIMENSION + count++, label);
        }

        dim = bubbleSpace.getSizeDimension();
        attrName = dim.getName();
        if (!StringUtils.isEmpty(attrName)) {
            label = attrName + "=" + block.getAttributeValue(attrName, getLocale());
            props.put(VISIO_PROPERTY_DIMENSION + count++, label);
        }

        dim = bubbleSpace.getColorDimension();
        attrName = dim.getName();
        if (!StringUtils.isEmpty(attrName)) {
            label = attrName + "=" + block.getAttributeValue(attrName, getLocale());
            props.put(VISIO_PROPERTY_DIMENSION + count++, label);
        }

        return props;
    }

    /**
     * Adds all necessary result nodes to the graph for the list of BuildingBlocks.
     *
     * @throws MasterNotFoundException
     */
    private void addResultNodes() throws MasterNotFoundException {
        LOGGER.debug("entering addResultNodes...");

        /*
         * If scaling is enabled, additional check is necessary, to estimate the size of the frame, in
         * which all to be drawn entities can be enclosed. The scaling factors for the X and Y axis are
         * then calculated respecting this frame.
         */
        if (portfolioOptions.isScalingEnabled()) {
            double tmp;
            double frameSizeX;
            double frameSizeY;
            double locMaxX = Double.MIN_VALUE;
            double locMaxY = Double.MIN_VALUE;

            for (BuildingBlock block : buildingBlocks) {
                // Determine min and max X
                tmp = bubbleSpace.getXDimension().getValue(block).doubleValue();
                if (tmp > locMaxX && tmp != -1) {
                    locMaxX = tmp;
                }
                if (tmp < locMinX && tmp != -1) {
                    locMinX = tmp;
                }
                // Determine min and max Y
                tmp = bubbleSpace.getYDimension().getValue(block).doubleValue();
                if (tmp > locMaxY && tmp != -1) {
                    locMaxY = tmp;
                }
                if (tmp < locMinY && tmp != -1) {
                    locMinY = tmp;
                }
            }
            // Estimate frame size, offsets and scaleFactor
            frameSizeX = locMaxX - locMinX;
            frameSizeY = locMaxY - locMinY;

            // Test whether all objects have the same x or y coordinate
            // If this is the case, scaling is being disabled in the corresponding dimension
            // If this check is not made, an infinity scale factor can occur
            if (frameSizeX == 0) {
                frameSizeX = 1;
            }
            if (frameSizeY == 0) {
                frameSizeY = 1;
            }

            scaleFactorX = (MAXIMUM_X - MINIMUM_X) / (frameSizeX * (MAXIMUM_X - MINIMUM_X));
            scaleFactorY = (MAXIMUM_Y - MINIMUM_Y) / (frameSizeY * (MAXIMUM_Y - MINIMUM_Y));

            offsetX = MINIMUM_X;
            offsetY = MINIMUM_Y;
        }

        // the oneDimensionNotSet is relevant only for testing purposes
        if (isOneDimensionNumber() || oneDimensionNotSet()) {
            for (BuildingBlock buildingBlock : buildingBlocks) {
                createBubbleShape(buildingBlock);
            }
        } else {
            createBubbles();
        }
        // Call the shape creation method for each shape

        LOGGER.debug("leaving addResultNodes...");
    }

    private double calculateMediumValueOfDiametersInTile(Map<BuildingBlock, Double> mappedDiameterToBuildingBlock,
            List<BuildingBlock> bBList) {
        double sum = 0;
        for (BuildingBlock block : bBList) {
            double diameterForBlock = mappedDiameterToBuildingBlock.get(block).doubleValue();
            sum = sum + diameterForBlock;
        }
        return sum / bBList.size();
    }

    /**
     * @param vertical
     *          true for a vertical rectangle, false means horizontal rectangle
     */
    private void createAxisValueRect(String text, boolean vertical, double weight, double dim, boolean dimIsNumber)
            throws MasterNotFoundException {
        double angle;
        Coordinates coords = null;
        if (vertical) {
            angle = VISIO_VERTICAL_ANGLE;
            if (dimIsNumber) {
                coords = new Coordinates(MINIMUM_X - AXIS_VALUES_FROM_COSY_DISTANCE,
                        (weight * (MAXIMUM_Y - MINIMUM_Y)) + MINIMUM_Y);
            } else {
                if (weight == 0.0) {
                    coords = new Coordinates(MINIMUM_X - AXIS_VALUES_FROM_COSY_DISTANCE,
                            (weight * (MAXIMUM_Y - MINIMUM_Y)) + MINIMUM_Y + dim / 2);
                } else {
                    if (weight == 1.0) {
                        coords = new Coordinates(MINIMUM_X - AXIS_VALUES_FROM_COSY_DISTANCE,
                                (weight * (MAXIMUM_Y - MINIMUM_Y)) + MINIMUM_Y - dim / 2);
                    } else {
                        coords = new Coordinates(MINIMUM_X - AXIS_VALUES_FROM_COSY_DISTANCE,
                                (weight * (MAXIMUM_Y - MINIMUM_Y)) + MINIMUM_Y);
                    }
                }
            }
        } else {
            angle = VISIO_HORIZONTAL_ANGLE;
            if (dimIsNumber) {
                coords = new Coordinates((weight * (MAXIMUM_X - MINIMUM_X)) + MINIMUM_X,
                        MINIMUM_Y - AXIS_VALUES_FROM_COSY_DISTANCE);
            } else {
                if (weight == 0.0) {
                    coords = new Coordinates((weight * (MAXIMUM_X - MINIMUM_X)) + dim / 2 + MINIMUM_X,
                            MINIMUM_Y - AXIS_VALUES_FROM_COSY_DISTANCE);
                } else {
                    if (weight == 1.0) {
                        coords = new Coordinates((weight * (MAXIMUM_X - MINIMUM_X)) - dim / 2 + MINIMUM_X,
                                MINIMUM_Y - AXIS_VALUES_FROM_COSY_DISTANCE);
                    } else {
                        coords = new Coordinates((weight * (MAXIMUM_X - MINIMUM_X)) + MINIMUM_X,
                                MINIMUM_Y - AXIS_VALUES_FROM_COSY_DISTANCE);
                    }
                }
            }
        }
        createRectStructure(text, angle, coords);
    }

    private void createBubbles() {

        List<Tile> tiles = createTiles();
        Map<BuildingBlock, Double> mapBuildingBlocksToDiameter = this.mapBuildingBlockToDiameter();

        scalingNeeded(mapBuildingBlocksToDiameter, tiles);

        //    double queryInfoHeight = createQueryInfo();

        if (scaling < 1) {
            doScaling(mapBuildingBlocksToDiameter);
        }

        for (Tile tile : tiles) {
            List<PositionInTile> possiblePositionsInTile;
            Map<BuildingBlock, Double> mapBuildingBlocksInTile = getMapBuildingBlocksInTile(
                    mapBuildingBlocksToDiameter, tile);
            Map<BuildingBlock, Double> sortedBuildingBlocksInTile = sortMap(mapBuildingBlocksInTile);
            double biggestDiameterInTile = findBiggestDiameterInTile(sortedBuildingBlocksInTile,
                    tile.getBuildingBlocks());
            if (portfolioOptions.getSizeAttributeId().intValue() > 0) {
                possiblePositionsInTile = possiblePositionsInTileWithScaling(sortedBuildingBlocksInTile, tile,
                        biggestDiameterInTile);
            } else {
                possiblePositionsInTile = possiblePositionsInTile(tile, biggestDiameterInTile);
            }

            int index = 0;

            // Create shapes
            for (Map.Entry<BuildingBlock, Double> entry : sortedBuildingBlocksInTile.entrySet()) {
                BuildingBlock block = entry.getKey();
                double diameter = mapBuildingBlocksToDiameter.get(block).doubleValue();
                double radius = diameter * 0.5;
                String hierarchicalBubbleName = getBuildingBlockHierarchicalName(block);
                String nonhierarchicalBubbleName = getBuildingBlockNonHierarchicalName(block);
                String screenName = getScreenName(portfolioOptions, nonhierarchicalBubbleName,
                        hierarchicalBubbleName, "", diameter, 8, "");

                PositionInTile position = possiblePositionsInTile.get(index);
                if (index < possiblePositionsInTile.size() - 1) {
                    index++;
                }

                double x = position.getX().doubleValue();
                double y = position.getY().doubleValue();

                try {
                    setBubbleShape(x, y, screenName, block, diameter, radius, false);
                } catch (MasterNotFoundException e) {
                    e.printStackTrace();
                }

            }
        }
    }

    private void createBubbleShape(BuildingBlock block) throws MasterNotFoundException {

        // calculate relative radius of bubble
        // ***********************************
        double weight = bubbleSpace.getSizeDimension().getValue(block).doubleValue();
        boolean missing = false;

        // if size isn't set then create a medium size bubble and set the
        // "missing" property (which currently leads to a hatched bubble)
        if (doubleEqual(weight, -1)) {
            weight = 0.4;

            // if no attribute is chosen for size then don't hatch the bubbles
            if (!"".equals(bubbleSpace.getSizeDimension().getName())) {
                missing = true;
            }
        }

        // the visio template specifies the diameter of a bubble as: 30mm * radius.
        // Calculation is done here since weight gets changed later
        double diameter = InchConverter.mmToInches(30) * (weight * 0.6 + 0.5);
        double radius = diameter * 0.5;

        // calculate x- and y-position
        // ***************************
        double x;
        double y;

        // Estimate x position:
        weight = bubbleSpace.getXDimension().getValue(block).doubleValue();

        // If undefined:
        if (doubleEqual(weight, -1)) {
            weight = 1.14;
        }

        // If there is no scaling:
        x = weight * (MAXIMUM_X - MINIMUM_X) + MINIMUM_X;

        // If there is scaling
        if (portfolioOptions.isScalingEnabled()) {
            x = offsetX + weight * (MAXIMUM_X - MINIMUM_X) * scaleFactorX
                    - locMinX * scaleFactorX * (MAXIMUM_X - MINIMUM_X);
        }

        // Explicitly set coordinate for undefined values:
        if (doubleEqual(weight, 1.14)) {
            x = weight * (MAXIMUM_X - MINIMUM_X) + MINIMUM_X;
        }

        // Estimate y position
        weight = bubbleSpace.getYDimension().getValue(block).doubleValue();

        // If undefined:
        if (doubleEqual(weight, -1)) {
            weight = 1.14;
        }

        // If there is no scaling:
        y = weight * (MAXIMUM_Y - MINIMUM_Y) + MINIMUM_Y;

        // If there is scaling
        if (portfolioOptions.isScalingEnabled()) {
            y = offsetY + weight * (MAXIMUM_Y - MINIMUM_Y) * scaleFactorY
                    - locMinY * scaleFactorY * (MAXIMUM_Y - MINIMUM_Y);
        }

        // Explicitly set coordinate for undefined values:
        if (doubleEqual(weight, 1.14)) {
            y = weight * (MAXIMUM_Y - MINIMUM_Y) + MINIMUM_Y;
        }

        // Get bubbles' name
        String hierarchicalBubbleName = getBuildingBlockHierarchicalName(block);
        String nonhierarchicalBubbleName = getBuildingBlockNonHierarchicalName(block);
        String screenName = getScreenName(portfolioOptions, nonhierarchicalBubbleName, hierarchicalBubbleName, "",
                diameter, 8, "");

        // create shape, size and position it
        setBubbleShape(x, y, screenName, block, diameter, radius, missing);

    }

    private double createColorLegend() throws MasterNotFoundException {
        double colorLegendHeightInch = 0;
        if (!"".equals(bubbleSpace.getColorDimension().getName())) {
            double colorLegendX = getTargetPage().getDocument().getMaster(VISIO_SHAPE_NAME_COLOR_BUBBLE)
                    .getShapes()[0].getPinX();
            Coordinates position = new Coordinates(colorLegendX, namesLegendFrameMaxY);
            colorLegendHeightInch = createColorLegend(portfolioOptions.getColorOptionsBean(), this.getTargetPage(),
                    position, VISIO_SHAPE_NAME_COLOR_BUBBLE, bubbleSpace.getColorDimension().getName(),
                    TypeOfBuildingBlock.getTypeOfBuildingBlockByString(portfolioOptions.getSelectedBbType()));
        }
        return colorLegendHeightInch;
    }

    private void createCoordinateLabels(String xLabel, String yLabel) throws MasterNotFoundException {

        Shape shape = this.getTargetPage().createNewShape(VISIO_SHAPE_NAME_XAXIS);
        shape.setFieldValue(xLabel);

        shape = this.getTargetPage().createNewShape(VISIO_SHAPE_NAME_YAXIS);
        shape.setFieldValue(yLabel);
        shape.setAngle(VISIO_VERTICAL_ANGLE);
    }

    private void createCoordinateSystem() throws MasterNotFoundException {
        this.getTargetPage().createNewShape(VISIO_SHAPE_NAME_COSY);
        createGridLines(bubbleSpace.getXDimension(), true);
        createGridLines(bubbleSpace.getYDimension(), false);
    }

    /**
     * Creates Labels for the grid of an axis of the portfolio matrix
     */
    private void createGridLabels(Dimension<Double> dim, boolean vertical) throws MasterNotFoundException {
        DimensionAdapter<?> adapter = dim.getAdapter();
        boolean isNumberAt = adapter instanceof AttributeAdapter;

        double heightTile = COSY_SIDE / bubbleSpace.getYDimension().getMapping().size();
        double widthTile = COSY_SIDE / bubbleSpace.getXDimension().getMapping().size();

        if (isNumberAt) {
            isNumberAt = (((AttributeAdapter) adapter).getAttributeType() instanceof NumberAT)
                    && !(adapter instanceof AttributeRangeAdapter);

        }
        Map<String, Double> mapping = dim.getMapping();
        List<String> values = new ArrayList<String>(mapping.keySet()); // dim.getValues();
        if (isNumberAt) {
            AttributeType attrType = ((AttributeAdapter) adapter).getAttributeType();
            NumberAT numberAt = (NumberAT) attrType;
            values = getNumberValuesForLegend(numberAt, dim.getValues(), portfolioOptions);
            if (!values.isEmpty()) {
                if (values.size() == 1) {
                    createAxisValueRect(values.get(0), vertical, 0.5, 0.0, isNumberAt);
                } else {
                    int size = values.size();
                    double stepFraction = 1.0 / (size - 1.0);
                    for (int i = 0; i < size; i++) {
                        createAxisValueRect(values.get(i), vertical, i * stepFraction, 0.0, isNumberAt);
                    }
                }
            }
        } else {
            for (int i = 0; i < values.size(); i++) {
                String value = values.get(i);
                if (vertical) {
                    createAxisValueRect(value, vertical, mapping.get(value).doubleValue(), heightTile, isNumberAt);
                } else {
                    createAxisValueRect(value, vertical, mapping.get(value).doubleValue(), widthTile, isNumberAt);
                }
            }
        }
        createAxisValueRect(MessageAccess.getStringOrNull(DimensionOptionsBean.DEFAULT_VALUE, getLocale()),
                vertical, 1.14, 0.0, isNumberAt);
    }

    private void createGridLines(Dimension<Double> dim, boolean vertical) throws MasterNotFoundException {
        DimensionAdapter<?> adapter = dim.getAdapter();
        boolean isNumberAt = adapter instanceof AttributeAdapter;

        if (isNumberAt) {
            isNumberAt = (((AttributeAdapter) adapter).getAttributeType() instanceof NumberAT)
                    && !(adapter instanceof AttributeRangeAdapter);
        }
        Map<String, Double> mapping = dim.getMapping();
        List<String> values = new ArrayList<String>(mapping.keySet()); // dim.getValues();
        if (isNumberAt) {
            AttributeType attrType = ((AttributeAdapter) adapter).getAttributeType();
            NumberAT numberAt = (NumberAT) attrType;
            values = getNumberValuesForLegend(numberAt, dim.getValues(), portfolioOptions);

            if (!values.isEmpty() && values.size() > 1) {
                int size = values.size();
                for (int i = 0; i < size - 1; i++) {
                    if (vertical) {
                        double widthTile = COSY_SIDE / (size - 1);
                        createLine(MINIMUM_X + i * widthTile, MINIMUM_Y, MINIMUM_Y + i * widthTile, MAXIMUM_Y,
                                VALUE_LINESTYLE_SOLID);
                    } else {
                        double heightTile = COSY_SIDE / (size - 1);
                        createLine(MINIMUM_X, MINIMUM_Y + i * heightTile, MAXIMUM_X, MINIMUM_Y + i * heightTile,
                                VALUE_LINESTYLE_SOLID);
                    }
                }
            }
        } else {
            for (int i = 1; i < values.size(); i++) {
                if (vertical) {
                    double widthTile = COSY_SIDE / values.size();
                    createLine(MINIMUM_X + i * widthTile, MINIMUM_Y, MINIMUM_Y + i * widthTile, MAXIMUM_Y,
                            VALUE_LINESTYLE_SOLID);
                } else {
                    double heightTile = COSY_SIDE / values.size();
                    createLine(MINIMUM_X, MINIMUM_Y + i * heightTile, MAXIMUM_X, MINIMUM_Y + i * heightTile,
                            VALUE_LINESTYLE_SOLID);
                }
            }
        }
    }

    private Shape createLegend(boolean realPosition) throws MasterNotFoundException {

        Shape visioLegendContainer = this.getTargetPage()
                .createNewShape(VisioAttributeLegend.VISIO_SHAPE_NAME_LEGEND_GROUP_CONTAINER);

        double legendHeight;
        double legendBaseY;

        // create dimension column
        // ***********************
        Shape shape = visioLegendContainer.createNewInnerShape(VISIO_SHAPE_NAME_DIMHEADER);
        shape.setFieldValue(MessageAccess.getStringOrNull("graphicalReport.headline", getLocale()));

        // Note that here we initialize some of the variables necessary to determine the frame for the
        // names legend.
        legendFrameBaseX = shape.getPinX();
        legendFrameWidth = shape.getWidth();
        legendHeight = 6 * shape.getHeight();
        legendBaseY = shape.getPinY() - legendHeight + shape.getHeight();

        shape.setPosition(0, legendHeight - shape.getHeight());

        shape = visioLegendContainer.createNewInnerShape(VISIO_SHAPE_NAME_DIMCONTENT);
        shape.setFieldValue(MessageAccess.getStringOrNull("reports.xaxis", getLocale()));
        double pinY = legendHeight - 2 * shape.getHeight();
        double height = shape.getHeight();
        shape.setPosition(0, pinY);

        shape = visioLegendContainer.createNewInnerShape(VISIO_SHAPE_NAME_DIMCONTENT);
        shape.setFieldValue(MessageAccess.getStringOrNull("reports.yaxis", getLocale()));
        shape.setPosition(0, pinY - height);

        shape = visioLegendContainer.createNewInnerShape(VISIO_SHAPE_NAME_DIMCONTENT);
        shape.setFieldValue(MessageAccess.getStringOrNull("reports.size", getLocale()));
        shape.setPosition(0, pinY - 2 * height);

        shape = visioLegendContainer.createNewInnerShape(VISIO_SHAPE_NAME_DIMCONTENT);
        shape.setFieldValue(MessageAccess.getStringOrNull("reports.color", getLocale()));
        shape.setPosition(0, pinY - 3 * height);

        shape = visioLegendContainer.createNewInnerShape(VISIO_SHAPE_NAME_DIMCONTENT);
        shape.setFieldValue(MessageAccess.getStringOrNull("reports.position", getLocale()));
        shape.setPosition(0, pinY - 4 * height);

        double secondColX = shape.getWidth();

        // create attribute column
        // ***********************
        shape = visioLegendContainer.createNewInnerShape(VISIO_SHAPE_NAME_ATTRHEADER);
        shape.setFieldValue(MessageAccess.getStringOrNull("global.attribute", getLocale()));
        shape.setPosition(secondColX, legendHeight - shape.getHeight());

        legendFrameWidth = legendFrameWidth + shape.getWidth();

        shape = visioLegendContainer.createNewInnerShape(VISIO_SHAPE_NAME_ATTRCONTENT);
        shape.setFieldValue(getFieldValueFromDimension(bubbleSpace.getXDimension()));
        shape.setPosition(secondColX, pinY);

        shape = visioLegendContainer.createNewInnerShape(VISIO_SHAPE_NAME_ATTRCONTENT);
        shape.setFieldValue(getFieldValueFromDimension(bubbleSpace.getYDimension()));
        shape.setPosition(secondColX, pinY - height);

        shape = visioLegendContainer.createNewInnerShape(VISIO_SHAPE_NAME_ATTRCONTENT);
        shape.setFieldValue(getFieldValueFromDimension(bubbleSpace.getSizeDimension()));
        shape.setPosition(secondColX, pinY - 2 * height);

        shape = visioLegendContainer.createNewInnerShape(VISIO_SHAPE_NAME_ATTRCONTENT);
        shape.setFieldValue(getFieldValueFromDimension(bubbleSpace.getColorDimension()));
        shape.setPosition(secondColX, pinY - 3 * height);

        shape = visioLegendContainer.createNewInnerShape(VISIO_SHAPE_NAME_ATTRCONTENT);
        if (realPosition) {
            shape.setFieldValue(MessageAccess.getStringOrNull("reports.position.description", getLocale()));
        } else {
            String positionText = MessageAccess.getStringOrNull("reports.position.description.discrete",
                    getLocale());
            //When the SVG Version of this diagramm is generated the last blank in this text is left out; an extra blank is added before
            //the final dot, blank which has to be taken out fpr the Visio version.
            String text = positionText.substring(0, positionText.length() - 2);
            text = text.concat(positionText.substring(positionText.length() - 1));
            shape.setFieldValue(text);
        }
        shape.setPosition(secondColX, pinY - 4 * height);

        visioLegendContainer.setSize(legendFrameWidth, legendHeight);
        visioLegendContainer.setPosition(legendFrameBaseX, legendBaseY);

        namesLegendFrameMaxY = legendBaseY - InchConverter.cmToInches(LEGEND_MARGIN_CM);

        return visioLegendContainer;
    }

    /**
     * Create a line.
     * 
     * @param startX
     * @param startY
     * @param endX
     * @param endY
     * @param style
     * @throws MasterNotFoundException
     */
    private void createLine(double startX, double startY, double endX, double endY, String style)
            throws MasterNotFoundException {

        Shape shape = this.getTargetPage().createNewShape(SHAPE_LINE);
        shape.setBeginPosition(startX, startY);
        shape.setEndPosition(endX, endY);

        // set shape's geometry
        // note: the index parameter is known from the visio template.
        shape.setFirstVertexOfGeometry(startX, startY);
        shape.setLineEnd(2, startX, startY);

        // format the line depending on the style
        if ("dottedFine".equals(style)) {
            shape.setLinePattern(10);
            shape.setLineWeight(InchConverter.ptToInches(0.54, 72));
        } else if ("dottedCoarse".equals(style)) {
            shape.setLinePattern(16);
            shape.setLineWeight(InchConverter.ptToInches(0.54, 72));
        }
        // solid
        else {
            shape.setLinePattern(1);
            shape.setLineWeight(InchConverter.ptToInches(0.72, 72));
        }
    }

    private Map<BuildingBlock, Weight> createMapBBToWeightXY() {
        Map<BuildingBlock, Weight> mappedWeightXYToBB = Maps.newHashMap();
        for (BuildingBlock block : buildingBlocks) {
            Double weightX = bubbleSpace.getXDimension().getValue(block);
            Double weightY = bubbleSpace.getYDimension().getValue(block);
            mappedWeightXYToBB.put(block, Weight.of(weightX, weightY));
        }
        return mappedWeightXYToBB;
    }

    private void createQueryInfo(Shape legend) throws MasterNotFoundException {
        List<Shape> queryInfo = null;
        if (portfolioOptions.isShowSavedQueryInfo()) {
            double width = InchConverter.inchesToCm(legendFrameWidth);
            queryInfo = createSavedQueryInfo(new Coordinates(0, 0), width, 11, portfolioOptions.getSavedQueryInfo(),
                    portfolioOptions.getServerUrl());
        }
        double queryInfoHeight = 0;
        if (queryInfo != null && !queryInfo.isEmpty()) {
            queryInfoHeight = getQueryInfoHeight(queryInfo);
            setQueryInfoPos(queryInfo, legendFrameBaseX,
                    getTargetPage().getHeight() - queryInfoHeight - DISTANCE_TO_MARGIN_INCHES * 2);
            legend.setPosition(legend.getPinX(), legend.getPinY() - queryInfoHeight);
            namesLegendFrameMaxY -= queryInfoHeight;
        }
    }

    private void createRectStructure(String text, double angle, Coordinates coords) throws MasterNotFoundException {

        Shape shape = this.getTargetPage().createNewShape(VISIO_SHAPE_NAME_RECT);
        shape.setPosition(coords.getX(), coords.getY());
        shape.setAngle(angle);
        shape.setFieldValue(text);
    }

    private double createSizeLegend() throws MasterNotFoundException {
        // Create size legend
        double sizeLegendHeightInch = 0;
        if (!"".equals(bubbleSpace.getSizeDimension().getName())) {
            SizeDimension sizeDimension = bubbleSpace.getSizeDimension();
            SizeDimension newSizeDimension;
            if (scaling < 1) {
                newSizeDimension = resize(sizeDimension);
            } else {
                newSizeDimension = sizeDimension;
            }
            VisioAttributeLegend<Double> sizeLegend = createSizeLegend(newSizeDimension);
            sizeLegendHeightInch = sizeLegend.getLegendHeightInInch();
            Coordinates pos = sizeLegend.getPosition();
            pos.setY(namesLegendFrameMaxY);
            sizeLegend.setPosition(pos);
        }
        return sizeLegendHeightInch;
    }

    /**
     * Creates the visio index structures for the size dimension.
     *
     * @param dimension
     *          The size dimension for which the index structures should be created.
     * @param pos
     *          position to draw the legend
     * @return The legend height in inch.
     * @throws MasterNotFoundException
     */
    private VisioAttributeLegend<Double> createSizeLegend(Dimension<Double> dimension)
            throws MasterNotFoundException {

        VisioAttributeLegend<Double> legend = new VisioSizeLegend();
        legend.initializeLegend(this.getTargetPage(), dimension, dimension.getName(), getLocale());
        legend.createLegendEntries(VISIO_SHAPE_NAME_SIZE_ENTITY);
        return legend;
    }

    private List<Tile> createTiles() {
        List<Tile> tiles = new ArrayList<Tile>();
        Map<BuildingBlock, Weight> mappedBBToWeightXY = createMapBBToWeightXY();
        int nrTilesX = bubbleSpace.getXDimension().getMapping().size();
        int nrTilesY = bubbleSpace.getYDimension().getMapping().size();

        double heightTile = COSY_SIDE / nrTilesY;
        double widthTile = COSY_SIDE / nrTilesX;

        for (BuildingBlock block : buildingBlocks) {
            Tile tile = new Tile();
            Weight pairWeightBB = mappedBBToWeightXY.get(block);
            double xWeightBB = pairWeightBB.getX().doubleValue();
            double yWeightBB = pairWeightBB.getY().doubleValue();
            double xCorLeftCornerTile;
            double yCorLeftCornerTile;
            if (xWeightBB < 0) {
                xCorLeftCornerTile = COSY_SIDE;
            } else {
                xCorLeftCornerTile = widthTile * Math.round(xWeightBB * (nrTilesX - 1));
            }
            if (yWeightBB < 0) {
                yCorLeftCornerTile = COSY_SIDE - heightTile / 2;
            } else {
                yCorLeftCornerTile = heightTile * Math.round(yWeightBB * (nrTilesY - 1));
            }
            boolean foundTile = false;
            for (Tile t : tiles) {
                if (Double.compare(t.getxPos(), xCorLeftCornerTile) == 0
                        && Double.compare(t.getyPos(), yCorLeftCornerTile) == 0) {
                    tile = t;
                    tile.getBuildingBlocks().add(block);
                    foundTile = true;
                    break;
                }
            }
            if (!foundTile) {
                tile.setxPos(xCorLeftCornerTile);
                tile.setyPos(yCorLeftCornerTile);
                tile.setWidth(widthTile);
                tile.setHeight(heightTile);
                List<BuildingBlock> bBL = new LinkedList<BuildingBlock>();
                bBL.add(block);
                tile.setBuildingBlocks(bBL);
                tiles.add(tile);
            }
        }
        return tiles;
    }

    private void doScaling(Map<BuildingBlock, Double> mapBuildingBlocksToDiameter) {
        for (Map.Entry<BuildingBlock, Double> entryBBDiameter : mapBuildingBlocksToDiameter.entrySet()) {
            double initialDiameter = entryBBDiameter.getValue().doubleValue();
            entryBBDiameter.setValue(Double.valueOf(initialDiameter * scaling));
        }
    }

    private boolean doubleEqual(double first, double second) {
        return Math.abs(first - second) < 0.000001;
    }

    private double findBiggestDiameterInTile(Map<BuildingBlock, Double> mappedDiameterToBuildingBlock,
            List<BuildingBlock> bBList) {
        double diameter = 0;
        for (BuildingBlock block : bBList) {
            double diameterForBlock = mappedDiameterToBuildingBlock.get(block).doubleValue();
            if (diameterForBlock > diameter) {
                diameter = diameterForBlock;
            }
        }
        return diameter;
    }

    private Map<BuildingBlock, Double> getMapBuildingBlocksInTile(
            Map<BuildingBlock, Double> mapBuildingBlocksToDiameter, Tile tile) {
        List<BuildingBlock> bbInTile = tile.getBuildingBlocks();
        Map<BuildingBlock, Double> mapBBInTile = new HashMap<BuildingBlock, Double>();
        for (Map.Entry<BuildingBlock, Double> entry : mapBuildingBlocksToDiameter.entrySet()) {
            if (bbInTile.contains(entry.getKey())) {
                mapBBInTile.put(entry.getKey(), entry.getValue());
            }
        }
        return mapBBInTile;
    }

    private String getTitle(String bbType) {
        StringBuilder title = new StringBuilder();
        title.append(MessageAccess.getStringOrNull("graphicalExport.portfolio.title", getLocale())).append(" - ");
        title.append(MessageAccess.getStringOrNull("graphicalExport.reportDate", getLocale())).append(" ");
        title.append(DateUtils.formatAsStringToLong(new Date(), getLocale())).append("\n");
        title.append(MessageAccess.getStringOrNull("graphicalExport.title.content", getLocale())).append(": ");
        title.append(MessageAccess.getStringOrNull(bbType, getLocale()));

        return title.toString();
    }

    private boolean isOneDimensionNumber() {
        DimensionAdapter<?> adapterX = bubbleSpace.getXDimension().getAdapter();
        boolean isNumberAtX = adapterX instanceof AttributeAdapter;
        if (isNumberAtX) {
            isNumberAtX = (((AttributeAdapter) adapterX).getAttributeType() instanceof NumberAT)
                    && !(adapterX instanceof AttributeRangeAdapter);
        }
        DimensionAdapter<?> adapterY = bubbleSpace.getYDimension().getAdapter();
        boolean isNumberAtY = adapterY instanceof AttributeAdapter;
        if (isNumberAtY) {
            isNumberAtY = (((AttributeAdapter) adapterY).getAttributeType() instanceof NumberAT)
                    && !(adapterY instanceof AttributeRangeAdapter);
        }
        return (isNumberAtX || isNumberAtY);
    }

    private Map<BuildingBlock, Double> mapBuildingBlockToDiameter() {
        Map<BuildingBlock, Double> mappedBuildingBlockToDiameter = Maps.newHashMap();
        for (BuildingBlock block : buildingBlocks) {
            double weight = bubbleSpace.getSizeDimension().getValue(block).doubleValue();
            if (doubleEqual(weight, -1)) {
                weight = 0.4;
            }
            double diameter = InchConverter.mmToInches(30) * (weight * 0.6 + 0.5);
            mappedBuildingBlockToDiameter.put(block, Double.valueOf(diameter));
        }
        return mappedBuildingBlockToDiameter;
    }

    private boolean oneDimensionNotSet() {
        return bubbleSpace.getXDimension().getMapping().isEmpty()
                || bubbleSpace.getYDimension().getMapping().isEmpty();
    }

    private List<PositionInTile> possiblePositionsInTile(Tile tile, double diameter) {
        List<PositionInTile> coorPairs = Lists.newArrayList();
        double spacing = 3;
        double spacingInInches = InchConverter.ptToInches(spacing, DEFAULT_SYSTEM_DPI);
        double radius = diameter / 2;
        double height = tile.getHeight();
        double width = tile.getWidth();
        double nextXPos = MINIMUM_X + tile.getxPos() + radius + spacingInInches;
        double nextYPos = MINIMUM_Y + tile.getyPos() + height - (radius + spacingInInches);
        double maxX = MINIMUM_X + tile.getxPos() + width - radius - spacingInInches;
        double maxY = MINIMUM_Y + tile.getyPos() + (radius + spacingInInches);
        while (nextXPos < maxX) {
            while (nextYPos > maxY) {
                coorPairs.add(PositionInTile.of(Double.valueOf(nextXPos), Double.valueOf(nextYPos)));
                nextYPos -= diameter + spacingInInches;
            }
            nextXPos += diameter + spacingInInches;
            nextYPos = MINIMUM_Y + tile.getyPos() + height - (radius + spacingInInches);
        }

        return coorPairs;
    }

    private List<PositionInTile> possiblePositionsInTileWithScaling(
            Map<BuildingBlock, Double> mapBuildingBlocksToDiameter, Tile tile, double diameter) {
        List<PositionInTile> coorPairs = Lists.newArrayList();
        double spacing = 3;
        double spacingInInches = InchConverter.ptToInches(spacing, DEFAULT_SYSTEM_DPI);
        double radius = diameter / 2;
        double height = tile.getHeight();
        double width = tile.getWidth();
        double nextXPos = MINIMUM_X + tile.getxPos() + radius + spacingInInches;
        double nextYPos = MINIMUM_Y + tile.getyPos() + height - (radius + spacingInInches);
        double baseMaxX = MINIMUM_X + tile.getxPos() + width - spacingInInches;
        double baseMaxY = MINIMUM_Y + tile.getyPos() + spacingInInches;
        double maxX = MINIMUM_X + tile.getxPos() + width - radius - spacingInInches;
        double maxY = MINIMUM_Y + tile.getyPos() + (radius + spacingInInches);
        Set<Entry<BuildingBlock, Double>> bBSet = mapBuildingBlocksToDiameter.entrySet();
        Iterator<Entry<BuildingBlock, Double>> it = bBSet.iterator();
        while (nextXPos < maxX) {
            double biggestDiameterCol = 0;
            double smallestDiameterCol = diameter;
            while (nextYPos > maxY) {
                coorPairs.add(PositionInTile.of(Double.valueOf(nextXPos), Double.valueOf(nextYPos)));
                if (it.hasNext()) {
                    Entry<BuildingBlock, Double> next = it.next();
                    double findNewDiameter = next.getValue().doubleValue();
                    biggestDiameterCol = findNewDiameter > biggestDiameterCol ? findNewDiameter
                            : biggestDiameterCol;
                    smallestDiameterCol = findNewDiameter < smallestDiameterCol ? findNewDiameter
                            : smallestDiameterCol;
                    nextYPos -= findNewDiameter + spacingInInches;
                    maxY = baseMaxY + smallestDiameterCol / 2;
                } else {
                    nextYPos -= diameter + spacingInInches;
                    maxY = baseMaxY + radius;
                }
            }
            if (it.hasNext()) {
                nextXPos += (biggestDiameterCol + smallestDiameterCol) / 2 + spacingInInches;
                maxX = baseMaxX - (biggestDiameterCol + smallestDiameterCol) / 4;
                if (nextXPos > maxX && nextXPos - maxX < smallestDiameterCol) {
                    maxX = baseMaxX;
                }
            } else {
                nextXPos += diameter + spacingInInches;
                maxX = baseMaxX - radius;
            }
            nextYPos = MINIMUM_Y + tile.getyPos() + height - (radius + spacingInInches);
        }

        return coorPairs;
    }

    /**
     * @param sizeDimension
     */
    private SizeDimension resize(SizeDimension sizeDimension) {
        SizeDimension sizeDim = new SizeDimension(sizeDimension.getAdapter());
        Map<String, Double> sizedimMap = new HashMap<String, Double>();
        Map<String, Double> sizeDimensionMap = sizeDimension.getMapping();
        for (Map.Entry<String, Double> entry : sizeDimensionMap.entrySet()) {
            double currentValue = entry.getValue().doubleValue();
            double newValue = currentValue * scaling;
            sizedimMap.put(entry.getKey(), Double.valueOf(newValue));
        }
        sizeDim.setMapping(sizedimMap);
        return sizeDim;
    }

    private void scalingNeeded(Map<BuildingBlock, Double> mapBuildingBlocksToDiameter, List<Tile> tiles) {
        int spacing = 6;
        double spacingInInches = InchConverter.ptToInches(spacing, DEFAULT_SYSTEM_DPI);
        for (Tile tile : tiles) {
            Map<BuildingBlock, Double> mapBuildingBlocksInTile = getMapBuildingBlocksInTile(
                    mapBuildingBlocksToDiameter, tile);
            double middleValueOfDiametersInTile = calculateMediumValueOfDiametersInTile(mapBuildingBlocksInTile,
                    tile.getBuildingBlocks());
            int nrBBTile = tile.getBuildingBlocks().size();
            long nrBBInRow = Math.round(Math.sqrt(nrBBTile) + 1);
            double neededDim = nrBBInRow * (middleValueOfDiametersInTile + spacingInInches);
            if (tile.getWidth() < neededDim || tile.getHeight() < neededDim) {
                double scalingWidth = tile.getWidth() / neededDim;
                double scalingHeight = tile.getHeight() / neededDim;
                double possibleScaling = scalingWidth < scalingHeight ? scalingWidth : scalingHeight;
                if (scaling > possibleScaling) {
                    scaling = possibleScaling;
                }
            }
        }
    }

    private Map<BuildingBlock, Double> sortMap(Map<BuildingBlock, Double> mapBuildingBlocksInTile) {
        List<Entry<BuildingBlock, Double>> list = new LinkedList<Entry<BuildingBlock, Double>>(
                mapBuildingBlocksInTile.entrySet());
        Collections.sort(list, new Comparator<Map.Entry<BuildingBlock, Double>>() {
            public int compare(Entry<BuildingBlock, Double> o1, Entry<BuildingBlock, Double> o2) {
                return -((Comparable<Double>) (o1).getValue()).compareTo((o2).getValue());
            }
        });

        Map<BuildingBlock, Double> result = new LinkedHashMap<BuildingBlock, Double>();
        for (Iterator<Entry<BuildingBlock, Double>> it = list.iterator(); it.hasNext();) {
            Map.Entry<BuildingBlock, Double> entry = it.next();
            result.put(entry.getKey(), entry.getValue());
        }
        return result;
    }

    private void setBubbleShape(double x, double y, String bubbleName, BuildingBlock block, double diameter,
            double radius, boolean missing) throws MasterNotFoundException {
        Shape shape = this.getTargetPage().createNewShape(VISIO_SHAPE_NAME_BUBBLE);
        shape.setPosition(x, y);
        shape.setFillForegroundColor(getColorStr(bubbleSpace.getColorDimension().getValue(block)), 0);

        // display bubble's text.
        shape.setShapeText(bubbleName);

        // adjust bubble's width and height to correct size.
        //
        // note for visio viewer:
        // it does not work to set xform/width and xform/height in the document. the bubble has to
        // be "modeled" via a first vertex and two arcs, that is, two half-circles in the case of
        // bubbles.
        // additionally, to behave correctly the center of rotation has to be set to the center of the
        // bubble. the formulas for calculating the values have been taken form the visio template.
        //
        // note:
        // the index parameters 2 and 3 for the setEllipticalArc() method are known values (by
        // inspecting
        // the resulting visio document). this is unfortunately a quick-n-dirty solution to get at the
        // correct elements.
        shape.setSize(diameter, diameter);
        shape.setLocPin(radius, radius);
        shape.setFirstVertexOfGeometry(0, radius);
        shape.setEllipticalArc(2, diameter, radius, radius, diameter, 0, 1);
        shape.setEllipticalArc(3, 0, radius, radius, 0, 0, 1);
        if (missing) {
            shape.setLinePattern(9);
        }

        // transform textfield to match bubble's position and size.
        //
        // note: the formulas for calculating the values have been taken from the visio template.
        // 20mm calcualtes to 0.787401575 inches.

        // We keep this as an example for the usage of the textXForm although it is no longer needed
        // after having introduced a names legend
        // double txtWidth = Math.max(radius * 0.875, 0.787401575);
        // double txtHeight = diameter * 0.75;
        // shape.setTextXForm(radius, radius, txtWidth, txtHeight, txtWidth * 0.5, txtHeight * 0.5, 0);

        // set calculated dimension properties to be able to access them in the visio document
        Map<String, Object> props = addLabels(block);
        shape.setCustomProperties(props);
    }

    private void revertYAxesForVisio() {
        if (!getAttributeTypeService().isNumberAT(portfolioOptions.getYAxisAttributeId())) {
            PositionDimension yDimension = bubbleSpace.getYDimension();
            Map<String, Double> mapping = yDimension.getMapping();
            Map<String, Double> newMapping = new HashMap<String, Double>();
            for (Entry<String, Double> entry : mapping.entrySet()) {
                double value = entry.getValue().doubleValue();
                newMapping.put(entry.getKey(), Double.valueOf(1.0 - value));
            }
            yDimension.setMapping(newMapping);
            bubbleSpace.setYDimension(yDimension);
        }
    }
}