net.sourceforge.plantuml.timingdiagram.TimingDiagram Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of plantuml-gplv2 Show documentation
Show all versions of plantuml-gplv2 Show documentation
PlantUML is a component that allows to quickly write diagrams from text.
// THIS FILE HAS BEEN GENERATED BY A PREPROCESSOR.
/* +=======================================================================
* |
* | PlantUML : a free UML diagram generator
* |
* +=======================================================================
*
* (C) Copyright 2009-2024, Arnaud Roques
*
* Project Info: https://plantuml.com
*
* If you like this project or if you find it useful, you can support us at:
*
* https://plantuml.com/patreon (only 1$ per month!)
* https://plantuml.com/liberapay (only 1€ per month!)
* https://plantuml.com/paypal
*
*
* PlantUML is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License V2.
*
* THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC
* LICENSE ("AGREEMENT"). [GNU General Public License V2]
*
* ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM CONSTITUTES
* RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT.
*
* You may obtain a copy of the License at
*
* https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* PlantUML can occasionally display sponsored or advertising messages. Those
* messages are usually generated on welcome or error images and never on
* functional diagrams.
* See https://plantuml.com/professional if you want to remove them
*
* Images (whatever their format : PNG, SVG, EPS...) generated by running PlantUML
* are owned by the author of their corresponding sources code (that is, their
* textual description in PlantUML language). Those images are not covered by
* this GPL v2 license.
*
* The generated images can then be used without any reference to the GPL v2 license.
* It is not even necessary to stipulate that they have been generated with PlantUML,
* although this will be appreciated by the PlantUML team.
*
* There is an exception : if the textual description in PlantUML language is also covered
* by any license, then the generated images are logically covered
* by the very same license.
*
* This is the IGY distribution (Install GraphViz by Yourself).
* You have to install GraphViz and to setup the GRAPHVIZ_DOT environment variable
* (see https://plantuml.com/graphviz-dot )
*
* Icons provided by OpenIconic : https://useiconic.com/open
* Archimate sprites provided by Archi : http://www.archimatetool.com
* Stdlib AWS provided by https://github.com/milo-minderbinder/AWS-PlantUML
* Stdlib Icons provided https://github.com/tupadr3/plantuml-icon-font-sprites
* ASCIIMathML (c) Peter Jipsen http://www.chapman.edu/~jipsen
* ASCIIMathML (c) David Lippman http://www.pierce.ctc.edu/dlippman
* CafeUndZopfli ported by Eugene Klyuchnikov https://github.com/eustas/CafeUndZopfli
* Brotli (c) by the Brotli Authors https://github.com/google/brotli
* Themes (c) by Brett Schwarz https://github.com/bschwarz/puml-themes
* Twemoji (c) by Twitter at https://twemoji.twitter.com/
*
*/
package net.sourceforge.plantuml.timingdiagram;
import java.io.IOException;
import java.io.OutputStream;
import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import net.sourceforge.plantuml.FileFormatOption;
import net.sourceforge.plantuml.UmlDiagram;
import net.sourceforge.plantuml.command.CommandExecutionResult;
import net.sourceforge.plantuml.core.DiagramDescription;
import net.sourceforge.plantuml.core.ImageData;
import net.sourceforge.plantuml.core.UmlSource;
import net.sourceforge.plantuml.klimt.UStroke;
import net.sourceforge.plantuml.klimt.UTranslate;
import net.sourceforge.plantuml.klimt.color.Colors;
import net.sourceforge.plantuml.klimt.color.HColor;
import net.sourceforge.plantuml.klimt.color.HColors;
import net.sourceforge.plantuml.klimt.creole.Display;
import net.sourceforge.plantuml.klimt.drawing.UGraphic;
import net.sourceforge.plantuml.klimt.font.StringBounder;
import net.sourceforge.plantuml.klimt.geom.XDimension2D;
import net.sourceforge.plantuml.klimt.shape.AbstractTextBlock;
import net.sourceforge.plantuml.klimt.shape.TextBlock;
import net.sourceforge.plantuml.klimt.shape.ULine;
import net.sourceforge.plantuml.klimt.shape.URectangle;
import net.sourceforge.plantuml.skin.UmlDiagramType;
import net.sourceforge.plantuml.stereo.Stereotype;
import net.sourceforge.plantuml.style.PName;
import net.sourceforge.plantuml.style.SName;
import net.sourceforge.plantuml.style.Style;
import net.sourceforge.plantuml.style.StyleSignatureBasic;
import net.sourceforge.plantuml.timingdiagram.graphic.IntricatedPoint;
import net.sourceforge.plantuml.timingdiagram.graphic.TimeArrow;
public class TimingDiagram extends UmlDiagram implements Clocks {
public static final double marginX1 = 5;
private final double marginX2 = 5;
private final Map codes = new HashMap();
private final Map players = new LinkedHashMap();
private final Map clocks = new HashMap();
private final List messages = new ArrayList<>();
private final List highlights = new ArrayList<>();
private final TimingRuler ruler = new TimingRuler(getSkinParam());
private TimeTick now;
private Player lastPlayer;
private TimeAxisStategy timeAxisStategy = TimeAxisStategy.AUTOMATIC;
private boolean compactByDefault = false;
public DiagramDescription getDescription() {
return new DiagramDescription("(Timing Diagram)");
}
public TimingDiagram(UmlSource source) {
super(source, UmlDiagramType.TIMING, null);
}
@Override
protected ImageData exportDiagramInternal(OutputStream os, int index, FileFormatOption fileFormatOption)
throws IOException {
return createImageBuilder(fileFormatOption).drawable(getTextMainBlock(fileFormatOption)).write(os);
}
@Override
protected TextBlock getTextMainBlock(FileFormatOption fileFormatOption) {
return new AbstractTextBlock() {
public void drawU(UGraphic ug) {
drawInternal(ug);
}
public XDimension2D calculateDimension(StringBounder stringBounder) {
final double withBeforeRuler = getPart1MaxWidth(stringBounder);
final double totalWith = withBeforeRuler + ruler.getWidth() + marginX1 + marginX2;
return new XDimension2D(totalWith, getHeightTotal(stringBounder));
}
};
}
private StyleSignatureBasic getStyleSignature() {
return StyleSignatureBasic.of(SName.root, SName.element, SName.timingDiagram);
}
private HColor black() {
final Style style = getStyleSignature().getMergedStyle(getSkinParam().getCurrentStyleBuilder());
return style.value(PName.LineColor).asColor(getSkinParam().getIHtmlColorSet());
}
private void drawInternal(UGraphic ug) {
ruler.ensureNotEmpty();
final StringBounder stringBounder = ug.getStringBounder();
final double part1MaxWidth = getPart1MaxWidth(stringBounder);
for (Player player : players.values()) {
final UGraphic ugPlayer = ug.apply(getUTranslateForPlayer(player, stringBounder));
final HColor generalBackgroundColor = player.getGeneralBackgroundColor();
if (generalBackgroundColor != null)
ugPlayer.apply(generalBackgroundColor).apply(generalBackgroundColor.bg())
.draw(URectangle.build(getWidthTotal(stringBounder), player.getFullHeight(stringBounder)));
}
final UTranslate widthPart1 = UTranslate.dx(part1MaxWidth);
if (compactByDefault == false)
drawBorder(ug);
ug = ug.apply(UTranslate.dx(marginX1));
drawHighlightsBack(ug.apply(widthPart1));
ruler.drawVlines(ug.apply(widthPart1), getHeightInner(stringBounder));
boolean first = true;
for (Player player : players.values()) {
final UGraphic ugPlayer = ug.apply(getUTranslateForPlayer(player, stringBounder));
final double caption = getHeightForCaptions(stringBounder);
if (first) {
if (player.isCompact() == false)
drawHorizontalSeparator(ugPlayer);
player.getPart1(part1MaxWidth, caption).drawU(ugPlayer);
player.getPart2().drawU(ugPlayer.apply(widthPart1).apply(UTranslate.dy(caption)));
} else {
if (player.isCompact() == false)
drawHorizontalSeparator(ugPlayer.apply(UTranslate.dy(caption)));
player.getPart1(part1MaxWidth, 0).drawU(ugPlayer.apply(UTranslate.dy(caption)));
player.getPart2().drawU(ugPlayer.apply(widthPart1).apply(UTranslate.dy(caption)));
}
first = false;
}
ug = ug.apply(widthPart1);
ruler.drawTimeAxis(ug.apply(getLastTranslate(stringBounder)), this.timeAxisStategy, codes);
for (TimeMessage timeMessage : messages)
drawMessages(ug, timeMessage);
drawHighlightsLines(ug);
}
private void drawHorizontalSeparator(UGraphic ug) {
final StringBounder stringBounder = ug.getStringBounder();
ug = ug.apply(black());
ug = ug.apply(getBorderStroke());
ug = ug.apply(UTranslate.dx(-marginX1));
ug.draw(ULine.hline(getWidthTotal(stringBounder)));
}
private void drawBorder(UGraphic ug) {
final StringBounder stringBounder = ug.getStringBounder();
final ULine border = ULine.vline(getLastTranslate(stringBounder).getDy());
ug = ug.apply(black()).apply(getBorderStroke());
ug.draw(border);
ug.apply(UTranslate.dx(getWidthTotal(stringBounder))).draw(border);
}
private UStroke getBorderStroke() {
return getStyleSignature().getMergedStyle(getCurrentStyleBuilder()).getStroke();
}
private UTranslate getLastTranslate(final StringBounder stringBounder) {
return getUTranslateForPlayer(null, stringBounder).compose(UTranslate.dy(getHeightForCaptions(stringBounder)));
}
private void drawHighlightsBack(UGraphic ug) {
final double height = getHeightInner(ug.getStringBounder());
for (Highlight highlight : highlights)
highlight.drawHighlightsBack(ug, ruler, height);
}
private void drawHighlightsLines(UGraphic ug) {
final double height = getHeightInner(ug.getStringBounder());
for (Highlight highlight : highlights) {
highlight.drawHighlightsLines(ug, ruler, height);
final double start = ruler.getPosInPixel(highlight.getTickFrom());
highlight.getCaption(getSkinParam()).drawU(ug.apply(new UTranslate(start + 3, 2)));
}
}
private double getHeightTotal(StringBounder stringBounder) {
return getHeightInner(stringBounder) + ruler.getHeight(stringBounder);
}
private double getHeightInner(StringBounder stringBounder) {
return getLastTranslate(stringBounder).getDy();
}
private double getHeightForCaptions(StringBounder stringBounder) {
double result = 0;
for (Highlight highlight : highlights) {
final TextBlock caption = highlight.getCaption(getSkinParam());
result = Math.max(result, caption.calculateDimension(stringBounder).getHeight());
}
return result;
}
private double getWidthTotal(final StringBounder stringBounder) {
return getPart1MaxWidth(stringBounder) + ruler.getWidth() + marginX1 + marginX2;
}
private double getPart1MaxWidth(StringBounder stringBounder) {
double width = 0;
for (Player player : players.values())
width = Math.max(width, player.getPart1(0, 0).calculateDimension(stringBounder).getWidth());
return width;
}
private void drawMessages(UGraphic ug, TimeMessage message) {
final Player player1 = message.getPlayer1();
final Player player2 = message.getPlayer2();
final StringBounder stringBounder = ug.getStringBounder();
final UTranslate translate1 = getUTranslateForPlayer(player1, stringBounder)
.compose(UTranslate.dy(getHeightForCaptions(stringBounder)));
final UTranslate translate2 = getUTranslateForPlayer(player2, stringBounder)
.compose(UTranslate.dy(getHeightForCaptions(stringBounder)));
final IntricatedPoint pt1 = player1.getTimeProjection(stringBounder, message.getTick1());
final IntricatedPoint pt2 = player2.getTimeProjection(stringBounder, message.getTick2());
if (pt1 == null || pt2 == null)
return;
final TimeArrow timeArrow = TimeArrow.create(pt1.translated(translate1), pt2.translated(translate2),
message.getLabel(), getSkinParam(), message);
timeArrow.drawU(ug);
}
private UTranslate getUTranslateForPlayer(Player candidat, StringBounder stringBounder) {
double y = 0;
for (Player player : players.values()) {
if (candidat == player)
return UTranslate.dy(y);
// if (y == 0) {
// y += getHeightHighlights(stringBounder);
// }
y += player.getFullHeight(stringBounder);
}
if (candidat == null)
return UTranslate.dy(y);
throw new IllegalArgumentException();
}
public CommandExecutionResult createRobustConcise(String code, String full, TimingStyle type, boolean compact,
Stereotype stereotype, HColor backColor) {
final Player player = new PlayerRobustConcise(type, full, getSkinParam(), ruler, compactByDefault || compact,
stereotype, backColor);
players.put(code, player);
lastPlayer = player;
return CommandExecutionResult.ok();
}
public CommandExecutionResult createClock(String code, String full, int period, int pulse, int offset,
boolean compact) {
final PlayerClock player = new PlayerClock(full, getSkinParam(), ruler, period, pulse, offset,
compactByDefault);
players.put(code, player);
clocks.put(code, player);
final TimeTick tick = new TimeTick(new BigDecimal(period), TimingFormat.DECIMAL);
ruler.addTime(tick);
return CommandExecutionResult.ok();
}
public PlayerAnalog createAnalog(String code, String full, boolean compact, Stereotype stereotype) {
final PlayerAnalog player = new PlayerAnalog(full, getSkinParam(), ruler, compactByDefault, stereotype);
players.put(code, player);
return player;
}
public CommandExecutionResult createBinary(String code, String full, boolean compact, Stereotype stereotype) {
final Player player = new PlayerBinary(full, getSkinParam(), ruler, compactByDefault, stereotype);
players.put(code, player);
return CommandExecutionResult.ok();
}
public TimeMessage createTimeMessage(Player player1, TimeTick time1, Player player2, TimeTick time2, String label) {
final TimeMessage message = new TimeMessage(new TickInPlayer(player1, time1), new TickInPlayer(player2, time2),
label, getSkinParam());
messages.add(message);
return message;
}
public void addTime(TimeTick time, String code) {
this.now = time;
ruler.addTime(time);
if (code != null)
this.codes.put(code, time);
}
public TimeTick getCodeValue(String code) {
return codes.get(code);
}
public void updateNow(TimeTick time) {
this.now = time;
}
public Player getPlayer(String code) {
return players.get(code);
}
public TimeTick getNow() {
return now;
}
public TimeTick getClockValue(String clockName, int nb) {
final PlayerClock clock = clocks.get(clockName);
if (clock == null)
return null;
return new TimeTick(new BigDecimal(nb * clock.getPeriod()), TimingFormat.DECIMAL);
}
public void setLastPlayer(Player player) {
this.lastPlayer = player;
}
public Player getLastPlayer() {
return lastPlayer;
}
public void scaleInPixels(long tick, long pixel) {
ruler.scaleInPixels(tick, pixel);
}
public CommandExecutionResult setTimeAxisStategy(TimeAxisStategy newStrategy) {
this.timeAxisStategy = newStrategy;
return CommandExecutionResult.ok();
}
public CommandExecutionResult highlight(TimeTick tickFrom, TimeTick tickTo, Display caption, Colors colors) {
this.highlights.add(new Highlight(getSkinParam(), tickFrom, tickTo, caption, colors));
return CommandExecutionResult.ok();
}
public void goCompactMode() {
this.compactByDefault = true;
}
private SimpleDateFormat sdf;
public CommandExecutionResult useDateFormat(String dateFormat) {
try {
this.sdf = new SimpleDateFormat(dateFormat, Locale.US);
} catch (Exception e) {
return CommandExecutionResult.error("Bad date format");
}
return CommandExecutionResult.ok();
}
@Override
public TimingFormat getTimingFormatDate() {
if (sdf == null)
return TimingFormat.DATE;
return TimingFormat.create(sdf);
}
}