Skip to content

Commit 627114b

Browse files
committed
Improved donut display!
1 parent 58edb81 commit 627114b

File tree

3 files changed

+96
-41
lines changed

3 files changed

+96
-41
lines changed

app/depict/src/main/java/org/openscience/cdk/depict/DepictionGenerator.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@
4343
import org.openscience.cdk.renderer.generators.IGeneratorParameter;
4444
import org.openscience.cdk.renderer.generators.standard.SelectionVisibility;
4545
import org.openscience.cdk.renderer.generators.standard.StandardGenerator;
46+
import org.openscience.cdk.renderer.generators.standard.StandardGenerator.DelocalisedDonutsBondDisplay;
47+
import org.openscience.cdk.renderer.generators.standard.StandardGenerator.ForceDelocalisedBondDisplay;
4648
import org.openscience.cdk.tools.LoggingToolFactory;
4749

4850
import javax.vecmath.Point2d;
@@ -1072,6 +1074,25 @@ public DepictionGenerator withFillToFit() {
10721074
true);
10731075
}
10741076

1077+
/**
1078+
* When aromaticity is set on bonds, display this in the diagram. IUPAC
1079+
* recommends depicting kekulé structures to avoid ambiguity but it's common
1080+
* practice to render delocalised rings "donuts" or "life buoys". With fused
1081+
* rings this can be somewhat confusing as you end up with three lines at
1082+
* the fusion point. <br>
1083+
* By default small rings are renders as donuts with dashed bonds used
1084+
* otherwise. You can use dashed bonds always by turning off the
1085+
* {@link DelocalisedDonutsBondDisplay}.
1086+
*
1087+
* @return new generator for method chaining
1088+
* @see ForceDelocalisedBondDisplay
1089+
* @see DelocalisedDonutsBondDisplay
1090+
*/
1091+
public DepictionGenerator withAromaticDisplay() {
1092+
return withParam(ForceDelocalisedBondDisplay.class,
1093+
true);
1094+
}
1095+
10751096
/**
10761097
* Low-level option method to set a rendering model parameter.
10771098
*

display/renderbasic/src/main/java/org/openscience/cdk/renderer/generators/standard/StandardBondGenerator.java

Lines changed: 55 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,6 @@
5656
import javax.vecmath.Vector2d;
5757
import java.awt.Color;
5858
import java.awt.Font;
59-
import java.awt.Shape;
6059
import java.util.ArrayList;
6160
import java.util.Arrays;
6261
import java.util.Collections;
@@ -104,6 +103,7 @@ final class StandardBondGenerator {
104103
// indexes of atoms and rings
105104
private final Map<IAtom, Integer> atomIndexMap = new HashMap<IAtom, Integer>();
106105
private final Map<IBond, IAtomContainer> ringMap;
106+
private final Set<IBond> donuts = new HashSet<>();
107107

108108
// parameters
109109
private final double scale;
@@ -116,7 +116,7 @@ final class StandardBondGenerator {
116116
private final Color foreground, annotationColor;
117117
private final boolean fancyBoldWedges, fancyHashedWedges;
118118
private final double annotationDistance, annotationScale;
119-
private final boolean aromaticDonuts = true;
119+
private final boolean delocalisedDonuts;
120120
private final Font font;
121121
private final ElementGroup annotations;
122122
private final boolean forceDelocalised;
@@ -159,6 +159,7 @@ private StandardBondGenerator(IAtomContainer container, AtomSymbol[] symbols, Re
159159
this.annotationScale = (1 / scale) * parameters.get(StandardGenerator.AnnotationFontScale.class);
160160
this.annotationColor = parameters.get(StandardGenerator.AnnotationColor.class);
161161
this.forceDelocalised = parameters.get(StandardGenerator.ForceDelocalisedBondDisplay.class);
162+
this.delocalisedDonuts = parameters.get(StandardGenerator.DelocalisedDonutsBondDisplay.class);
162163
this.font = font;
163164

164165
// foreground is based on the carbon color
@@ -180,40 +181,55 @@ static IRenderingElement[] generateBonds(IAtomContainer container, AtomSymbol[]
180181
double stroke, Font font, ElementGroup annotations) {
181182
StandardBondGenerator bondGenerator = new StandardBondGenerator(container, symbols, parameters, annotations,
182183
font, stroke);
183-
IRenderingElement[] elements = new IRenderingElement[container.getBondCount()];
184+
IRenderingElement[] elements;
185+
186+
IRenderingElement donuts = bondGenerator.generateDonuts();
187+
if (donuts != null) {
188+
elements = new IRenderingElement[container.getBondCount()+1];
189+
elements[elements.length-1] = donuts;
190+
} else {
191+
elements = new IRenderingElement[container.getBondCount()];
192+
}
193+
184194
for (int i = 0; i < container.getBondCount(); i++) {
185195
final IBond bond = container.getBond(i);
186196
if (!StandardGenerator.isHidden(bond)) {
187197
elements[i] = bondGenerator.generate(bond);
188198
}
189199
}
190-
if (bondGenerator.aromaticDonuts) {
191-
IRenderingElement donuts = bondGenerator.generateDonuts();
192-
if (donuts != null) {
193-
elements = Arrays.copyOf(elements, elements.length + 1);
194-
elements[elements.length-1] = donuts;
195-
}
196-
}
197200
return elements;
198201
}
199202

203+
boolean canDelocalise(final IAtomContainer ring) {
204+
boolean okay = ring.getBondCount() <= 8;
205+
if (!okay)
206+
return false;
207+
for (IBond bond : ring.bonds()) {
208+
if (!bond.isAromatic())
209+
okay = false;
210+
if ((bond.getOrder() != null &&
211+
bond.getOrder() != UNSET) &&
212+
!forceDelocalised)
213+
okay = false;
214+
}
215+
return okay;
216+
}
217+
200218
IRenderingElement generateDonuts() {
219+
if (!delocalisedDonuts)
220+
return null;
201221
ElementGroup group = new ElementGroup();
202222
Set<IAtomContainer> rings = new HashSet<>(ringMap.values());
203223
for (IAtomContainer ring : rings) {
204-
if (ring.getBondCount() <= 8) {
205-
Point2d p2 = GeometryUtil.get2DCenter(ring);
206-
if (ring.getBondCount() == 5) {
207-
TextOutline to = new TextOutline("–", font).resize(1/scale, -1/scale);
208-
to = to.translate(p2.x-to.getCenter().getX(),
209-
p2.y-to.getCenter().getY());
210-
// group.add(GeneralPath.shapeOf(to.getOutline(), foreground));
211-
}
212-
double s = GeometryUtil.getBondLengthMedian(ring);
213-
double n = ring.getBondCount();
214-
double r = s / (2*Math.tan(Math.PI/n));
215-
// group.add(new OvalElement(p2.x, p2.y, r-separation, false, foreground));
216-
}
224+
if (!canDelocalise(ring))
225+
continue;
226+
for (IBond bond : ring.bonds())
227+
donuts.add(bond);
228+
Point2d p2 = GeometryUtil.get2DCenter(ring);
229+
double s = GeometryUtil.getBondLengthMedian(ring);
230+
double n = ring.getBondCount();
231+
double r = s / (2*Math.tan(Math.PI/n));
232+
group.add(new OvalElement(p2.x, p2.y, r-separation, false, foreground));
217233
}
218234
return group;
219235
}
@@ -238,16 +254,18 @@ IRenderingElement generate(IBond bond) {
238254
case SINGLE:
239255
// TODO check small ring!
240256
if (bond.isAromatic()) {
241-
if (forceDelocalised && !aromaticDonuts) {
242-
elem = generateDoubleBond(bond, true);
243-
} else
257+
if (donuts.contains(bond))
258+
elem = generateSingleBond(bond, atom1, atom2);
259+
else if (forceDelocalised)
260+
elem = generateDoubleBond(bond, forceDelocalised);
261+
else
244262
elem = generateSingleBond(bond, atom1, atom2);
245263
} else
246264
elem = generateSingleBond(bond, atom1, atom2);
247265
break;
248266
case DOUBLE:
249267
if (bond.isAromatic()) {
250-
if (forceDelocalised && aromaticDonuts)
268+
if (donuts.contains(bond))
251269
elem = generateSingleBond(bond, atom1, atom2);
252270
else
253271
elem = generateDoubleBond(bond, forceDelocalised);
@@ -259,7 +277,10 @@ IRenderingElement generate(IBond bond) {
259277
break;
260278
default:
261279
if (bond.isAromatic() && order == UNSET) {
262-
elem = generateDoubleBond(bond, true);
280+
if (donuts.contains(bond))
281+
elem = generateSingleBond(bond, atom1, atom2);
282+
else
283+
elem = generateDoubleBond(bond, true);
263284
} else {
264285
// bond orders > 3 not supported
265286
elem = generateDashedBond(atom1, atom2);
@@ -700,17 +721,17 @@ private IRenderingElement generateDoubleBond(IBond bond, boolean arom) {
700721
}
701722
} else if (!(hasDisplayedSymbol(atom1) && hasDisplayedSymbol(atom2))) {
702723
if (atom1Bonds.size() == 1 && atom2Bonds.isEmpty())
703-
return generateOffsetDoubleBond(bond, atom1, atom2, atom1Bonds.get(0), atom2Bonds, dashed);
724+
return generateOffsetDoubleBond(bond, atom1, atom2, atom1Bonds.get(0), atom2Bonds, arom);
704725
else if (atom2Bonds.size() == 1 && atom1Bonds.isEmpty())
705-
return generateOffsetDoubleBond(bond, atom2, atom1, atom2Bonds.get(0), atom1Bonds, dashed);
726+
return generateOffsetDoubleBond(bond, atom2, atom1, atom2Bonds.get(0), atom1Bonds, arom);
706727
else if (specialOffsetBondNextToWedge(atom1, atom1Bonds))
707-
return generateOffsetDoubleBond(bond, atom1, atom2, selectPlainSingleBond(atom1Bonds), atom2Bonds, dashed);
728+
return generateOffsetDoubleBond(bond, atom1, atom2, selectPlainSingleBond(atom1Bonds), atom2Bonds, arom);
708729
else if (specialOffsetBondNextToWedge(atom2, atom2Bonds))
709-
return generateOffsetDoubleBond(bond, atom2, atom1, selectPlainSingleBond(atom2Bonds), atom1Bonds, dashed);
730+
return generateOffsetDoubleBond(bond, atom2, atom1, selectPlainSingleBond(atom2Bonds), atom1Bonds, arom);
710731
else if (atom1Bonds.size() == 1)
711-
return generateOffsetDoubleBond(bond, atom1, atom2, atom1Bonds.get(0), atom2Bonds, dashed);
732+
return generateOffsetDoubleBond(bond, atom1, atom2, atom1Bonds.get(0), atom2Bonds, arom);
712733
else if (atom2Bonds.size() == 1)
713-
return generateOffsetDoubleBond(bond, atom2, atom1, atom2Bonds.get(0), atom1Bonds, dashed);
734+
return generateOffsetDoubleBond(bond, atom2, atom1, atom2Bonds.get(0), atom1Bonds, arom);
714735
else
715736
return generateCenteredDoubleBond(bond, atom1, atom2, atom1Bonds, atom2Bonds);
716737
} else {

display/renderbasic/src/main/java/org/openscience/cdk/renderer/generators/standard/StandardGenerator.java

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -168,12 +168,12 @@ public static enum HighlightStyle {
168168
private final IGeneratorParameter<?> atomColor = new AtomColor(), visibility = new Visibility(),
169169
strokeRatio = new StrokeRatio(), separationRatio = new BondSeparation(), wedgeRatio = new WedgeRatio(),
170170
marginRatio = new SymbolMarginRatio(), hatchSections = new HashSpacing(), dashSections = new DashSection(),
171-
waveSections = new WaveSpacing(), fancyBoldWedges = new FancyBoldWedges(),
171+
waveSections = new WaveSpacing(), fancyBoldWedges = new FancyBoldWedges(),
172172
fancyHashedWedges = new FancyHashedWedges(), highlighting = new Highlighting(),
173-
glowWidth = new OuterGlowWidth(), annCol = new AnnotationColor(), annDist = new AnnotationDistance(),
174-
annFontSize = new AnnotationFontScale(), sgroupBracketDepth = new SgroupBracketDepth(),
175-
sgroupFontScale = new SgroupFontScale(), omitMajorIsotopes = new OmitMajorIsotopes(),
176-
forceDonuts = new ForceDelocalisedBondDisplay();
173+
glowWidth = new OuterGlowWidth(), annCol = new AnnotationColor(), annDist = new AnnotationDistance(),
174+
annFontSize = new AnnotationFontScale(), sgroupBracketDepth = new SgroupBracketDepth(),
175+
sgroupFontScale = new SgroupFontScale(), omitMajorIsotopes = new OmitMajorIsotopes(),
176+
forceDelocalised = new ForceDelocalisedBondDisplay(), delocaliseDonuts = new DelocalisedDonutsBondDisplay();
177177

178178
/**
179179
* Create a new standard generator that utilises the specified font to display atom symbols.
@@ -606,8 +606,8 @@ public static IRenderingElement embedText(Font font, String text, Color color, d
606606
@Override
607607
public List<IGeneratorParameter<?>> getParameters() {
608608
return Arrays.asList(atomColor, visibility, strokeRatio, separationRatio, wedgeRatio, marginRatio,
609-
hatchSections, dashSections, waveSections, fancyBoldWedges, fancyHashedWedges, highlighting, glowWidth,
610-
annCol, annDist, annFontSize, sgroupBracketDepth, sgroupFontScale, omitMajorIsotopes, forceDonuts);
609+
hatchSections, dashSections, waveSections, fancyBoldWedges, fancyHashedWedges, highlighting, glowWidth,
610+
annCol, annDist, annFontSize, sgroupBracketDepth, sgroupFontScale, omitMajorIsotopes, forceDelocalised, delocaliseDonuts);
611611
}
612612

613613
static String getAnnotationLabel(IChemObject chemObject) {
@@ -1154,4 +1154,17 @@ public Boolean getDefault() {
11541154
return false;
11551155
}
11561156
}
1157+
1158+
/**
1159+
* Render small delocalised rings as donuts/life buoys? This can sometimes
1160+
* be misleading for fused rings but is commonly used.
1161+
*/
1162+
public static final class DelocalisedDonutsBondDisplay extends AbstractGeneratorParameter<Boolean> {
1163+
1164+
/**{@inheritDoc} */
1165+
@Override
1166+
public Boolean getDefault() {
1167+
return true;
1168+
}
1169+
}
11571170
}

0 commit comments

Comments
 (0)