use of org.w3c.dom.svg.SVGElement in project megameklab by MegaMek.
the class PrintRecordSheet method setArmorPips.
// Older method that was unsuitable for mechs but could work for vees and aerospace. Would need
// some updating to work with regions rather than fixed pips in the SVG.
protected void setArmorPips(Element group, int armorVal, boolean symmetric) {
final String METHOD_NAME = "setArmorPips(SVGElement,int)";
// First sort pips into rows. We can't rely on the pips to be in order, so we use
// maps to allow non-sequential loading.
Map<Integer, Map<Integer, Element>> rowMap = new TreeMap<>();
int pipCount = 0;
for (int i = 0; i < group.getChildNodes().getLength(); i++) {
final Node n = group.getChildNodes().item(i);
if (n instanceof SVGElement) {
final SVGElement pip = (SVGElement) n;
try {
int index = pip.getId().indexOf(":");
String[] coords = pip.getId().substring(index + 1).split(",");
int r = Integer.parseInt(coords[0]);
rowMap.putIfAbsent(r, new TreeMap<>());
rowMap.get(r).put(Integer.parseInt(coords[1]), pip);
pipCount++;
} catch (Exception ex) {
MegaMekLab.getLogger().log(getClass(), METHOD_NAME, LogLevel.ERROR, "Malformed id for SVG armor pip element: " + pip.getId());
}
}
}
if (pipCount < armorVal) {
MegaMekLab.getLogger().log(getClass(), METHOD_NAME, LogLevel.ERROR, "Armor pip group " + ((SVGElement) group).getId() + " does not contain enough pips for " + armorVal + " armor");
return;
} else if (pipCount == armorVal) {
// Simple case; leave as is
return;
}
// Convert map into array for easier iteration in both directions. This will also skip
// over gaps in the numbering.
Element[][] rows = new Element[rowMap.size()][];
int row = 0;
for (Map<Integer, Element> r : rowMap.values()) {
rows[row] = new Element[r.size()];
int i = 0;
for (Element e : r.values()) {
rows[row][i] = e;
i++;
}
row++;
}
// Get the ratio of the number of pips to show to the total number of pips
// and distribute the number of pips proportionally to each side
double saturation = Math.min(1.0, (double) armorVal / pipCount);
// Now we find the center row, which is the row that has the same number of pips above
// and below it as nearly as possible.
int centerRow = rows.length / 2;
int pipsAbove = 0;
for (int r = 0; r < rows.length; r++) {
pipsAbove += rows[r].length;
if (pipsAbove > pipCount / 2) {
centerRow = r;
break;
}
}
int showAbove = (int) Math.round(pipsAbove * saturation);
int showBelow = armorVal - showAbove;
// keep a running total of the number to hide in each row
int[] showByRow = new int[rows.length];
double remaining = pipsAbove;
for (int i = centerRow; i >= 0; i--) {
showByRow[i] = (int) Math.round(rows[i].length * showAbove / remaining);
if (symmetric && (showByRow[i] > 0) && (showByRow[i] % 2) != (rows[i].length % 2)) {
if ((showByRow[i] < showAbove) && (showByRow[i] < rows[i].length)) {
showByRow[i]++;
} else {
showByRow[i]--;
}
}
showAbove -= showByRow[i];
remaining -= rows[i].length;
}
// We may have some odd ones left over due to symmetry imposed on middle pip of the row
showBelow += showAbove;
remaining = pipCount - pipsAbove;
for (int i = centerRow + 1; i < rows.length; i++) {
showByRow[i] = (int) Math.round(rows[i].length * showBelow / remaining);
if (symmetric && (showByRow[i] > 0) && (showByRow[i] % 2) != (rows[i].length % 2)) {
if ((showByRow[i] < showBelow) && (showByRow[i] < rows[i].length)) {
showByRow[i]++;
} else {
showByRow[i]--;
}
}
showBelow -= showByRow[i];
remaining -= rows[i].length;
}
// Now we need to deal with leftovers, starting in the middle and adding one or two at a time
// (depending on whether there are an odd or even number of pips in the row) moving out toward
// the top and bottom and repeating until they are all placed.
remaining = showBelow;
while (remaining > 0) {
for (int i = 0; i <= centerRow; i++) {
row = centerRow - i;
int toAdd = symmetric ? (2 - rows[row].length % 2) : 1;
if (remaining < toAdd) {
continue;
}
if (rows[row].length >= showByRow[row] + toAdd) {
showByRow[row] += toAdd;
remaining -= toAdd;
}
if (i > 0) {
row = centerRow + i;
if (row >= rows.length) {
continue;
}
toAdd = symmetric ? (2 - rows[row].length % 2) : 1;
if (remaining < toAdd) {
continue;
}
if (rows[row].length >= showByRow[row] + toAdd) {
showByRow[row] += toAdd;
remaining -= toAdd;
}
}
}
}
// Now select which pips in each row to hide
for (row = 0; row < rows.length; row++) {
int toHide = rows[row].length - showByRow[row];
if (toHide == 0) {
continue;
}
double ratio = (double) toHide / rows[row].length;
int length = rows[row].length;
if (symmetric) {
length /= 2;
if (toHide % 2 == 1) {
hideElement(rows[row][length]);
toHide--;
ratio = (double) toHide / (rows[row].length - 1);
}
}
Set<Integer> indices = new HashSet<>();
double accum = 0.0;
for (int i = length % 2; i < length; i += 2) {
accum += ratio;
if (accum >= 1 - saturation) {
indices.add(i);
accum -= 1.0;
toHide--;
if (symmetric) {
indices.add(rows[row].length - 1 - i);
toHide--;
}
}
if (toHide == 0) {
break;
}
}
if (toHide > 0) {
for (int i = length - 1; i >= 0; i -= 2) {
accum += ratio;
if (accum >= saturation) {
indices.add(i);
accum -= 1.0;
toHide--;
if (symmetric) {
indices.add(rows[row].length - 1 - i);
toHide--;
}
}
if (toHide == 0) {
break;
}
}
}
int i = 0;
while (toHide > 0) {
if (!indices.contains(i)) {
indices.add(i);
toHide--;
if (symmetric) {
indices.add(rows[row].length - 1 - i);
toHide--;
}
}
i++;
}
for (int index : indices) {
hideElement(rows[row][index]);
}
}
}
use of org.w3c.dom.svg.SVGElement in project elki by elki-project.
the class BatikUtil method getRelativeCoordinates.
/**
* Get the relative coordinates of a point within the coordinate system of a
* particular SVG Element.
*
* @param evt Event, needs to be a DOMMouseEvent
* @param reference SVG Element the coordinate system is used of
* @return Array containing the X and Y values
*/
public static double[] getRelativeCoordinates(Event evt, Element reference) {
if (evt instanceof DOMMouseEvent && reference instanceof SVGLocatable && reference instanceof SVGElement) {
// Get the screen (pixel!) coordinates
DOMMouseEvent gnme = (DOMMouseEvent) evt;
SVGMatrix mat = ((SVGLocatable) reference).getScreenCTM();
SVGMatrix imat = mat.inverse();
SVGPoint cPt = ((SVGElement) reference).getOwnerSVGElement().createSVGPoint();
cPt.setX(gnme.getClientX());
cPt.setY(gnme.getClientY());
// Have Batik transform the screen (pixel!) coordinates into SVG element
// coordinates
cPt = cPt.matrixTransform(imat);
return new double[] { cPt.getX(), cPt.getY() };
}
return null;
}
use of org.w3c.dom.svg.SVGElement in project megameklab by MegaMek.
the class PrintRecordSheet method addPips.
/**
* Adds pips to the SVG diagram. The rows are defined in the SVG diagram with a series of <rect>
* elements, each of which determines the bounds of a row of pips. The spacing between pips is
* determined by the height of the first row. If rows overlap the pips are offset by half in the next
* row.
*
* @param group A <g> element that has <rect> children that describe pip rows
* @param pipCount The number of pips to place in the region
* @param symmetric If true, the left and right sides will be mirror images (assuming the row
* bounds are symmetric). Used for regions on a unit's center line.
* @param size The ratio of pip radius to the spacing between pips.
* @param strokeWidth The value to use for the stroke-width attribute when drawing the pips.
*/
protected void addPips(Element group, int pipCount, boolean symmetric, PipType pipType, double size, double strokeWidth) {
if (pipCount == 0) {
return;
}
final String METHOD_NAME = "addArmorPips(SVGElement,int)";
double spacing = 6.15152;
double left = Double.MAX_VALUE;
double top = Double.MAX_VALUE;
double right = 0;
double bottom = 0;
List<Rectangle2D> regions = new ArrayList<>();
for (int i = 0; i < group.getChildNodes().getLength(); i++) {
final Node r = group.getChildNodes().item(i);
if (r instanceof SVGRectElement) {
Rectangle2D bbox = getRectBBox((SVGRectElement) r);
if (bbox.getX() < left) {
left = bbox.getX();
}
if (bbox.getY() < top) {
top = bbox.getY();
}
if (bbox.getX() + bbox.getWidth() > right) {
right = bbox.getX() + bbox.getWidth();
}
if (bbox.getY() + bbox.getHeight() > bottom) {
bottom = bbox.getY() + bbox.getHeight();
}
regions.add(bbox);
}
}
if (regions.isEmpty()) {
MegaMekLab.getLogger().log(getClass(), METHOD_NAME, LogLevel.WARNING, "No pip rows defined for region " + group.getAttribute("id"));
return;
}
Rectangle2D bounds = new Rectangle2D.Double(left, top, right - left, bottom - top);
double aspect = bounds.getWidth() / bounds.getHeight();
double centerLine = regions.get(0).getX() + regions.get(0).getWidth() / 2.0;
int maxWidth = 0;
Collections.sort(regions, (r1, r2) -> (int) r1.getY() - (int) r2.getY());
// Maximum number of pips that can be displayed on each row
int[] rowLength = null;
int[][] halfPipCount = null;
int totalPips = 0;
double scale = 1.0;
List<Rectangle2D> rows = null;
while (totalPips < pipCount) {
totalPips = 0;
rows = rescaleRows(regions, scale);
rowLength = new int[rows.size()];
halfPipCount = new int[rows.size()][];
double prevRowBottom = 0;
int centerPip = 0;
spacing = rows.stream().mapToDouble(Rectangle2D::getHeight).min().orElse(spacing);
for (int i = 0; i < rows.size(); i++) {
final Rectangle2D rect = rows.get(i);
int halfPipsLeft = (int) ((centerLine - rect.getX()) / (spacing / 2));
int halfPipsRight = (int) ((rect.getX() + rect.getWidth() - centerLine) / (spacing / 2));
if ((i > 0) && (rect.getY() < prevRowBottom)) {
centerPip = (1 - centerPip);
if (halfPipsLeft % 2 != centerPip) {
halfPipsLeft--;
}
if (halfPipsRight % 2 != centerPip) {
halfPipsRight--;
}
rowLength[i] = (halfPipsLeft + halfPipsRight) / 2;
} else {
rowLength[i] = (halfPipsLeft + halfPipsRight) / 2;
centerPip = rowLength[i] % 2;
}
if (rowLength[i] > maxWidth) {
maxWidth = rowLength[i];
}
halfPipCount[i] = new int[] { halfPipsLeft, halfPipsRight };
totalPips += rowLength[i];
prevRowBottom = rect.getY() + spacing;
}
scale *= 0.9;
}
;
int nRows = adjustedRows(pipCount, rows.size(), maxWidth, aspect);
// Now we need to select the rows to use. If the total pips available in those rows is
// insufficient, add a row and try again.
int available = 0;
int minWidth = maxWidth;
List<Integer> useRows = new ArrayList<>();
while (available < pipCount) {
int start = rows.size() / (nRows * 2);
for (int i = 0; i < nRows; i++) {
int r = start + i * rows.size() / nRows;
if (rowLength[r] > 0) {
useRows.add(r);
available += rowLength[r];
if (rowLength[r] < minWidth) {
minWidth = rowLength[r];
}
}
}
if (available < pipCount) {
nRows++;
available = 0;
useRows.clear();
minWidth = maxWidth;
}
}
// Sort the rows into the order pips should be added: longest rows first, then for rows of
// equal length the one closest to the middle first
final int rowCount = rows.size();
final int[] rowSize = Arrays.copyOf(rowLength, rowLength.length);
Collections.sort(useRows, (r1, r2) -> {
if (rowSize[r1] == rowSize[r2]) {
return Math.abs(r1 - rowCount / 2) - Math.abs(r2 - rowCount / 2);
} else {
return rowSize[r2] - rowSize[r1];
}
});
// Now we iterate through the rows and assign pips as many times as it takes to get all assigned.
int[] pipsByRow = new int[rows.size()];
int remaining = pipCount;
while (remaining > 0) {
for (int r : useRows) {
if (rowLength[r] > pipsByRow[r]) {
int toAdd = Math.min(remaining, Math.min(rowLength[r] / minWidth, rowLength[r] - pipsByRow[r]));
pipsByRow[r] += toAdd;
remaining -= toAdd;
}
}
}
// an odd number of pips.
if (symmetric) {
// First we remove all the odd pips in even rows
remaining = 0;
for (int r = 0; r < rows.size(); r++) {
if ((rowLength[r] % 2 == 0) && (pipsByRow[r] % 2 == 1)) {
pipsByRow[r]--;
remaining++;
}
}
// Now we go through all the selected rows and assign them; this time even rows can
// only be assigned pips in pairs.
int toAdd = 0;
boolean added = false;
do {
for (int r : useRows) {
toAdd = 2 - rowLength[r] % 2;
if ((remaining >= toAdd) && (pipsByRow[r] + toAdd <= rowLength[r])) {
pipsByRow[r] += toAdd;
remaining -= toAdd;
}
}
} while ((remaining > 0) && added);
// We may still have one or more left. At this point all rows are considered available.
int centerRow = rows.size() / 2;
while (remaining > 0) {
for (int i = 0; i <= centerRow; i++) {
int r = centerRow - i;
toAdd = 2 - rowLength[r] % 2;
if (remaining < toAdd) {
continue;
}
if (rowLength[r] >= pipsByRow[r] + toAdd) {
pipsByRow[r] += toAdd;
remaining -= toAdd;
}
if (i > 0) {
r = centerRow + i;
if (r >= rows.size()) {
continue;
}
toAdd = 2 - rowLength[r] % 2;
if (remaining < toAdd) {
continue;
}
if (rowLength[r] >= pipsByRow[r] + toAdd) {
pipsByRow[r] += toAdd;
remaining -= toAdd;
}
}
}
// with the remaining pip to one of the even rows.
if (remaining == 1) {
boolean noSingle = true;
int fromRow = -1;
for (int r = 0; r < rows.size(); r++) {
if (rowLength[r] % 2 == 1) {
if (pipsByRow[r] < rowLength[r]) {
noSingle = false;
break;
} else {
fromRow = r;
}
}
}
if (noSingle) {
pipsByRow[fromRow]--;
remaining++;
}
}
}
}
// It's likely that there's extra spacing between rows, so we're going to check whether
// we can increase horizontal spacing between pips to keep the approximate aspect ratio.
int firstRow = 0;
int lastRow = rows.size() - 1;
int r = 0;
while (r < rows.size()) {
if (pipsByRow[r] > 0) {
firstRow = r;
break;
}
r++;
}
r = rows.size() - 1;
while (r >= 0) {
if (pipsByRow[r] > 0) {
lastRow = r;
break;
}
r--;
}
double targetWidth = aspect * (rows.get(lastRow).getY() + rows.get(lastRow).getHeight() - rows.get(firstRow).getY());
double hSpacing = targetWidth / pipsByRow[firstRow] - spacing;
for (r = firstRow + 1; r <= lastRow; r++) {
if (pipsByRow[r] > 0) {
hSpacing = Math.min(hSpacing, (Math.min(targetWidth, rows.get(r).getWidth()) - spacing) / pipsByRow[r]);
}
}
if (hSpacing < spacing) {
hSpacing = spacing;
}
for (r = 0; r < pipsByRow.length; r++) {
if (pipsByRow[r] > 0) {
double radius = rows.get(r).getHeight() * size;
Element pip = null;
// Symmetric and this row is centered
if (symmetric && (halfPipCount[r][0] == halfPipCount[r][1])) {
double leftX = centerLine - hSpacing;
double rightX = centerLine;
if (rowLength[r] % 2 == 1) {
leftX -= radius;
rightX += hSpacing - radius;
if (pipsByRow[r] % 2 == 1) {
pip = createPip(leftX + hSpacing, rows.get(r).getY(), radius, strokeWidth, pipType);
group.appendChild(pip);
pipsByRow[r]--;
}
} else {
leftX += hSpacing / 2 - radius;
rightX += hSpacing / 2 - radius;
}
while (pipsByRow[r] > 0) {
pip = createPip(leftX, rows.get(r).getY(), radius, strokeWidth, pipType);
group.appendChild(pip);
pip = createPip(rightX, rows.get(r).getY(), radius, strokeWidth, pipType);
group.appendChild(pip);
leftX -= hSpacing;
rightX += hSpacing;
pipsByRow[r] -= 2;
}
} else {
// If the location is symmetric but the middle of the current row is to the left
// of the centerline, right justify. If non-symmetric, balance the extra space at the
// ends of the rows with any odd space going on the right margin.
double x = centerLine - halfPipCount[r][0] * spacing / 2.0;
if (symmetric && halfPipCount[r][0] > halfPipCount[r][1]) {
x += (rowLength[r] - pipsByRow[r]) * hSpacing;
} else if (!symmetric) {
x += ((rowLength[r] - pipsByRow[r]) / 2) * hSpacing;
}
while (pipsByRow[r] > 0) {
pip = createPip(x, rows.get(r).getY(), radius, strokeWidth, pipType);
group.appendChild(pip);
pipsByRow[r]--;
x += hSpacing;
}
}
}
}
}
Aggregations