CommandChartLine.java
/* ========================================================================
* 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/paypal
*
* This file is part of PlantUML.
*
* PlantUML is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* PlantUML 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 General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
* USA.
*
*
* Original Author: David Fyfe
*
*/
package net.sourceforge.plantuml.chart.command;
import java.util.ArrayList;
import java.util.List;
import net.sourceforge.plantuml.chart.ChartDiagram;
import net.sourceforge.plantuml.chart.ChartSeries;
import net.sourceforge.plantuml.command.CommandExecutionResult;
import net.sourceforge.plantuml.command.ParserPass;
import net.sourceforge.plantuml.command.SingleLineCommand2;
import net.sourceforge.plantuml.klimt.color.HColor;
import net.sourceforge.plantuml.klimt.color.NoSuchColorException;
import net.sourceforge.plantuml.regex.IRegex;
import net.sourceforge.plantuml.regex.RegexConcat;
import net.sourceforge.plantuml.regex.RegexLeaf;
import net.sourceforge.plantuml.regex.RegexOptional;
import net.sourceforge.plantuml.regex.RegexResult;
import net.sourceforge.plantuml.stereo.Stereotype;
import net.sourceforge.plantuml.utils.LineLocation;
public class CommandChartLine extends SingleLineCommand2<ChartDiagram> {
public CommandChartLine() {
super(false, getRegexConcat());
}
static IRegex getRegexConcat() {
return RegexConcat.build(CommandChartLine.class.getName(), RegexLeaf.start(), //
new RegexLeaf("line"), //
RegexLeaf.spaceZeroOrMore(), //
new RegexOptional(new RegexLeaf(1, "STEREO", "(\\<\\<.+?\\>\\>)")), //
RegexLeaf.spaceZeroOrMore(), //
new RegexOptional(new RegexLeaf(1, "NAME", "\"([^\"]+)\"")), //
RegexLeaf.spaceZeroOrMore(), //
new RegexLeaf(1, "DATA", "\\[(.*)\\]"), //
RegexLeaf.spaceZeroOrMore(), //
new RegexOptional(new RegexLeaf(1, "COLOR", "#([0-9a-fA-F]{6}|[0-9a-fA-F]{3}|\\w+)")), //
new RegexOptional( //
new RegexConcat( //
RegexLeaf.spaceOneOrMore(), //
new RegexLeaf(1, "V2", "(v2)"))), //
new RegexOptional( //
new RegexConcat( //
RegexLeaf.spaceOneOrMore(), //
new RegexLeaf(1, "LABELS", "(labels)"))), //
RegexLeaf.end());
}
@Override
protected CommandExecutionResult executeArg(ChartDiagram diagram, LineLocation location, RegexResult arg,
ParserPass currentPass) throws NoSuchColorException {
final String stereo = arg.getLazzy("STEREO", 0);
final String name = arg.getLazzy("NAME", 0);
final String data = arg.get("DATA", 0);
final String colorStr = arg.getLazzy("COLOR", 0);
// Check if data contains coordinate pairs (x,y) format
final ChartSeries series;
final String seriesName = name != null ? name : "line" + diagram.getSeries().size();
if (data.contains("(")) {
// Parse coordinate pairs
final List<Double> xValues = new ArrayList<>();
final List<Double> yValues = new ArrayList<>();
if (!parseCoordinatePairs(data, xValues, yValues))
return CommandExecutionResult.error("Invalid coordinate pair format in line data");
series = new ChartSeries(seriesName, ChartSeries.SeriesType.LINE, xValues, yValues);
} else {
// Parse traditional y-values only
final List<Double> values = parseValues(data);
if (values == null)
return CommandExecutionResult.error("Invalid number format in line data");
series = new ChartSeries(seriesName, ChartSeries.SeriesType.LINE, values);
}
if (stereo != null) {
series.setStereotype(Stereotype.build(stereo));
}
if (colorStr != null) {
final HColor color = diagram.getSkinParam().getIHtmlColorSet().getColor("#" + colorStr);
series.setColor(color);
}
// Check if this line should use the secondary v-axis
final String v2Str = arg.getLazzy("V2", 0);
if (v2Str != null) {
series.setUseSecondaryAxis(true);
}
// Check if labels keyword was present
final String labelsStr = arg.getLazzy("LABELS", 0);
if (labelsStr != null) {
series.setShowLabels(true);
}
return diagram.addSeries(series);
}
private List<Double> parseValues(String data) {
final List<Double> result = new ArrayList<>();
if (data == null || data.trim().isEmpty())
return result;
final String[] parts = data.split(",");
for (String part : parts) {
try {
result.add(Double.parseDouble(part.trim()));
} catch (NumberFormatException e) {
return null;
}
}
return result;
}
private boolean parseCoordinatePairs(String data, List<Double> xValues, List<Double> yValues) {
if (data == null || data.trim().isEmpty())
return false;
// Match pattern: (x,y) or (x, y)
// Split by closing paren followed by comma and opening paren
final String cleaned = data.replaceAll("\\s+", ""); // Remove all whitespace
final String[] pairs = cleaned.split("\\),\\(");
for (String pair : pairs) {
// Clean up the pair - remove leading/trailing parens and brackets
String trimmedPair = pair.trim();
trimmedPair = trimmedPair.replaceAll("^[\\[\\(]+", ""); // Remove leading [ or (
trimmedPair = trimmedPair.replaceAll("[\\]\\)]+$", ""); // Remove trailing ] or )
final String[] coords = trimmedPair.split(",");
if (coords.length != 2)
return false;
try {
final double x = Double.parseDouble(coords[0].trim());
final double y = Double.parseDouble(coords[1].trim());
xValues.add(x);
yValues.add(y);
} catch (NumberFormatException e) {
return false;
}
}
return true;
}
}