org.openscience.cdk.renderer.ReactionSetRenderer Maven / Gradle / Ivy
/* Copyright (C) 2008-2009 Gilleain Torrance
* 2008-2009 Arvid Berg
* 2009 Stefan Kuhn
* 2009 Egon Willighagen
*
* Contact: [email protected]
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2.1
* of the License, or (at your option) any later version.
*
* 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package org.openscience.cdk.renderer;
import java.awt.Rectangle;
import java.awt.geom.Rectangle2D;
import java.util.List;
import javax.vecmath.Point2d;
import org.openscience.cdk.interfaces.IAtomContainer;
import org.openscience.cdk.interfaces.IChemModel;
import org.openscience.cdk.interfaces.IReaction;
import org.openscience.cdk.interfaces.IReactionSet;
import org.openscience.cdk.renderer.elements.ElementGroup;
import org.openscience.cdk.renderer.elements.IRenderingElement;
import org.openscience.cdk.renderer.font.IFontManager;
import org.openscience.cdk.renderer.generators.BasicSceneGenerator.BondLength;
import org.openscience.cdk.renderer.generators.BasicSceneGenerator.Scale;
import org.openscience.cdk.renderer.generators.IGenerator;
import org.openscience.cdk.renderer.visitor.IDrawVisitor;
/**
* A general renderer for {@link IChemModel}s, {@link IReaction}s, and
* {@link IAtomContainer}s. The chem object
* is converted into a 'diagram' made up of {@link IRenderingElement}s. It takes
* an {@link IDrawVisitor} to do the drawing of the generated diagram. Various
* display properties can be set using the {@link RendererModel}.
*
* This class has several usage patterns. For just painting fit-to-screen do:
*
* renderer.paintMolecule(molecule, visitor, drawArea)
*
* for painting at a scale determined by the bond length in the RendererModel:
*
* if (moleculeIsNew) {
* renderer.setup(molecule, drawArea);
* }
* Rectangle diagramSize = renderer.paintMolecule(molecule, visitor);
* // ...update scroll bars here
*
* to paint at full screen size, but not resize with each change:
*
* if (moleculeIsNew) {
* renderer.setScale(molecule);
* Rectangle diagramBounds = renderer.calculateDiagramBounds(molecule);
* renderer.setZoomToFit(diagramBounds, drawArea);
* renderer.paintMolecule(molecule, visitor);
* } else {
* Rectangle diagramSize = renderer.paintMolecule(molecule, visitor);
* // ...update scroll bars here
* }
*
* finally, if you are scrolling, and have not changed the diagram:
*
* renderer.repaint(visitor)
*
* will just repaint the previously generated diagram, at the same scale.
*
* There are two sets of methods for painting IChemObjects - those that take
* a Rectangle that represents the desired draw area, and those that return a
* Rectangle that represents the actual draw area. The first are intended for
* drawing molecules fitted to the screen (where 'screen' means any drawing
* area) while the second type of method are for drawing bonds at the length
* defined by the {@link RendererModel} parameter bondLength.
*
* There are two numbers used to transform the model so that it fits on screen.
* The first is scale, which is used to map model coordinates to
* screen coordinates. The second is zoom which is used to, well,
* zoom the on screen coordinates. If the diagram is fit-to-screen, then the
* ratio of the bounds when drawn using bondLength and the bounds of
* the screen is used as the zoom.
*
* So, if the bond length on screen is set to 40, and the average bond length
* of the model is 2 (unitless, but roughly Ångstrom scale) then the
* scale will be 20. If the model is 10 units wide, then the diagram drawn at
* 100% zoom will be 10 * 20 = 200 in width on screen. If the screen is 400
* pixels wide, then fitting it to the screen will make the zoom 200%. Since the
* zoom is just a floating point number, 100% = 1 and 200% = 2.
*
* @author maclean
* @cdk.module renderextra
* @cdk.githash
*/
public class ReactionSetRenderer extends AbstractRenderer implements IRenderer {
private IRenderer reactionRenderer;
/**
* A renderer that generates diagrams using the specified
* generators and manages fonts with the supplied font manager.
*
* @param generators
* a list of classes that implement the IGenerator interface
* @param fontManager
* a class that manages mappings between zoom and font sizes
*/
public ReactionSetRenderer(List> generators, IFontManager fontManager) {
this(new RendererModel(), generators, fontManager);
for (IGenerator generator : generators) {
rendererModel.registerParameters(generator);
}
}
public ReactionSetRenderer(RendererModel rendererModel, List> generators,
IFontManager fontManager) {
super(rendererModel);
this.fontManager = fontManager;
reactionRenderer = new ReactionRenderer(rendererModel, generators, fontManager);
this.setup();
}
/**
* A renderer that generates diagrams using the specified
* generators for {@link IAtomContainer}s and {@link IReactionSet}s
* and manages fonts with the supplied font manager.
*
* @param generators
* a list of classes that implement the {@link IGenerator} interface
* @param reactionGenerators
* a list of classes that implement the {@link IGenerator} interface
* @param fontManager
* a class that manages mappings between zoom and font sizes
*/
public ReactionSetRenderer(List> generators,
List> reactionGenerators, IFontManager fontManager) {
this(new RendererModel(), generators, reactionGenerators, fontManager);
}
protected ReactionSetRenderer(RendererModel rendererModel, List> generators,
List> reactionGenerators, IFontManager fontManager) {
super(rendererModel);
this.fontManager = fontManager;
reactionRenderer = new ReactionRenderer(generators, reactionGenerators, fontManager);
this.setup();
}
/**
* Setup the transformations necessary to draw this Reaction Set.
*
* @param reactionSet
* @param screen
*/
@Override
public void setup(IReactionSet reactionSet, Rectangle screen) {
this.setScale(reactionSet);
Rectangle2D bounds = BoundsCalculator.calculateBounds(reactionSet);
this.modelCenter = new Point2d(bounds.getCenterX(), bounds.getCenterY());
this.drawCenter = new Point2d(screen.getCenterX(), screen.getCenterY());
this.setup();
}
/**
* Set the scale for an IReactionSet. It calculates the average bond length
* of the model and calculates the multiplication factor to transform this
* to the bond length that is set in the RendererModel.
*
* @param reactionSet
*/
@Override
public void setScale(IReactionSet reactionSet) {
double bondLength = AverageBondLengthCalculator.calculateAverageBondLength(reactionSet);
double scale = this.calculateScaleForBondLength(bondLength);
// store the scale so that other components can access it
this.rendererModel.getParameter(Scale.class).setValue(scale);
}
/** {@inheritDoc} */
@Override
public Rectangle paint(IReactionSet reactionSet, IDrawVisitor drawVisitor) {
// total up the bounding boxes
Rectangle2D totalBounds = BoundsCalculator.calculateBounds(reactionSet);
// setup and draw
this.setupTransformNatural(totalBounds);
ElementGroup diagram = new ElementGroup();
for (IReaction reaction : reactionSet.reactions()) {
diagram.add(reactionRenderer.generateDiagram(reaction));
}
this.paint(drawVisitor, diagram);
// the size of the painted diagram is returned
return this.convertToDiagramBounds(totalBounds);
}
/**
* Paint a set of reactions.
*
* @param reactionSet the reaction to paint
* @param drawVisitor the visitor that does the drawing
* @param bounds the bounds on the screen
* @param resetCenter
* if true, set the draw center to be the center of bounds
*/
@Override
public void paint(IReactionSet reactionSet, IDrawVisitor drawVisitor, Rectangle2D bounds, boolean resetCenter) {
// total up the bounding boxes
Rectangle2D totalBounds = BoundsCalculator.calculateBounds(reactionSet);
this.setupTransformToFit(bounds, totalBounds,
AverageBondLengthCalculator.calculateAverageBondLength(reactionSet), resetCenter);
ElementGroup diagram = new ElementGroup();
for (IReaction reaction : reactionSet.reactions()) {
diagram.add(reactionRenderer.generateDiagram(reaction));
}
// paint them all
this.paint(drawVisitor, diagram);
}
/** {@inheritDoc} */
@Override
public Rectangle calculateDiagramBounds(IReactionSet reactionSet) {
return this.calculateScreenBounds(BoundsCalculator.calculateBounds(reactionSet));
}
/**
* Given a bond length for a model, calculate the scale that will transform
* this length to the on screen bond length in RendererModel.
*
* @param modelBondLength the wanted model bond length in screen length
* @return returns the scale that causes the drawn bond lengths in pixels to match
* the given model bond length
*/
@Override
public double calculateScaleForBondLength(double modelBondLength) {
if (Double.isNaN(modelBondLength) || modelBondLength == 0) {
return rendererModel.getParameter(Scale.class).getDefault();
} else {
return this.rendererModel.getParameter(BondLength.class).getValue() / modelBondLength;
}
}
/** {@inheritDoc} */
@Override
public List> getGenerators() {
// return reactionRenderer.getReactionGenerators();
return null;
}
}