| /* ==================================================================== |
| Licensed to the Apache Software Foundation (ASF) under one or more |
| contributor license agreements. See the NOTICE file distributed with |
| this work for additional information regarding copyright ownership. |
| The ASF licenses this file to You under the Apache License, Version 2.0 |
| (the "License"); you may not use this file except in compliance with |
| the License. You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| 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. |
| ==================================================================== */ |
| |
| package org.apache.poi.xdgf.usermodel.section.geometry; |
| |
| import java.awt.geom.AffineTransform; |
| import java.awt.geom.Arc2D; |
| import java.awt.geom.Point2D; |
| |
| import org.apache.poi.POIXMLException; |
| import org.apache.poi.xdgf.usermodel.XDGFCell; |
| import org.apache.poi.xdgf.usermodel.XDGFShape; |
| |
| import com.microsoft.schemas.office.visio.x2012.main.CellType; |
| import com.microsoft.schemas.office.visio.x2012.main.RowType; |
| |
| public class EllipticalArcTo implements GeometryRow { |
| |
| EllipticalArcTo _master = null; |
| |
| // The x-coordinate of the ending vertex on an arc. |
| Double x = null; |
| |
| // The y-coordinate of the ending vertex on an arc. |
| Double y = null; |
| |
| // The x-coordinate of the arc's control point; a point on the arc. The |
| // control point is best located about halfway between the beginning and |
| // ending vertices of the arc. Otherwise, the arc may grow to an extreme |
| // size in order to pass through the control point, with unpredictable |
| // results. |
| Double a = null; |
| |
| // The y-coordinate of an arc's control point. |
| Double b = null; |
| |
| // The angle of an arc's major axis relative to the x-axis of its parent |
| // shape. |
| Double c = null; |
| |
| // The ratio of an arc's major axis to its minor axis. Despite the usual |
| // meaning of these words, the "major" axis does not have to be greater than |
| // the "minor" axis, so this ratio does not have to be greater than 1. |
| // Setting this cell to a value less than or equal to 0 or greater than 1000 |
| // can lead to unpredictable results. |
| Double d = null; |
| |
| Boolean deleted = null; |
| |
| // TODO: support formulas |
| |
| public EllipticalArcTo(RowType row) { |
| |
| if (row.isSetDel()) |
| deleted = row.getDel(); |
| |
| for (CellType cell : row.getCellArray()) { |
| String cellName = cell.getN(); |
| |
| if (cellName.equals("X")) { |
| x = XDGFCell.parseDoubleValue(cell); |
| } else if (cellName.equals("Y")) { |
| y = XDGFCell.parseDoubleValue(cell); |
| } else if (cellName.equals("A")) { |
| a = XDGFCell.parseDoubleValue(cell); |
| } else if (cellName.equals("B")) { |
| b = XDGFCell.parseDoubleValue(cell); |
| } else if (cellName.equals("C")) { |
| c = XDGFCell.parseDoubleValue(cell); |
| } else if (cellName.equals("D")) { |
| d = XDGFCell.parseDoubleValue(cell); |
| } else { |
| throw new POIXMLException("Invalid cell '" + cellName |
| + "' in EllipticalArcTo row"); |
| } |
| } |
| } |
| |
| public boolean getDel() { |
| if (deleted != null) |
| return deleted; |
| |
| if (_master != null) |
| return _master.getDel(); |
| |
| return false; |
| } |
| |
| public Double getX() { |
| return x == null ? _master.x : x; |
| } |
| |
| public Double getY() { |
| return y == null ? _master.y : y; |
| } |
| |
| public Double getA() { |
| return a == null ? _master.a : a; |
| } |
| |
| public Double getB() { |
| return b == null ? _master.b : b; |
| } |
| |
| public Double getC() { |
| return c == null ? _master.c : c; |
| } |
| |
| public Double getD() { |
| return d == null ? _master.d : d; |
| } |
| |
| @Override |
| public void setupMaster(GeometryRow row) { |
| _master = (EllipticalArcTo) row; |
| } |
| |
| public static int draw = 0; |
| |
| @Override |
| public void addToPath(java.awt.geom.Path2D.Double path, XDGFShape parent) { |
| |
| if (getDel()) |
| return; |
| |
| // intentionally shadowing variables here |
| double x = getX(); |
| double y = getY(); |
| double a = getA(); |
| double b = getB(); |
| double c = getC(); |
| double d = getD(); |
| |
| createEllipticalArc(x, y, a, b, c, d, path); |
| } |
| |
| public static void createEllipticalArc(double x, double y, double a, |
| double b, double c, double d, java.awt.geom.Path2D.Double path) { |
| |
| // Formula for center of ellipse by Junichi Yoda & nashwaan: |
| // -> From http://visguy.com/vgforum/index.php?topic=2464.0 |
| // |
| // x1,y1 = start; x2,y2 = end; x3,y3 = control point |
| // |
| // x0 = |
| // ((x1-x2)*(x1+x2)*(y2-y3)-(x2-x3)*(x2+x3)*(y1-y2)+D^2*(y1-y2)*(y2-y3)*(y1-y3))/(2*((x1-x2)*(y2-y3)-(x2-x3)*(y1-y2))) |
| // y0 = |
| // ((x1-x2)*(x2-x3)*(x1-x3)/D^2+(x2-x3)*(y1-y2)*(y1+y2)-(x1-x2)*(y2-y3)*(y2+y3))/(2*((x2-x3)*(y1-y2)-(x1-x2)*(y2-y3))) |
| // radii along axis: a = sqrt{ (x1-x0)^2 + (y1-y0)^2 * D^2 } |
| // |
| |
| Point2D last = path.getCurrentPoint(); |
| double x0 = last.getX(); |
| double y0 = last.getY(); |
| |
| // translate all of the points to the same angle as the ellipse |
| AffineTransform at = AffineTransform.getRotateInstance(-c); |
| double[] pts = new double[] { x0, y0, x, y, a, b }; |
| at.transform(pts, 0, pts, 0, 3); |
| |
| x0 = pts[0]; |
| y0 = pts[1]; |
| x = pts[2]; |
| y = pts[3]; |
| a = pts[4]; |
| b = pts[5]; |
| |
| // nasty math time |
| |
| double d2 = d * d; |
| double cx = ((x0 - x) * (x0 + x) * (y - b) - (x - a) * (x + a) |
| * (y0 - y) + d2 * (y0 - y) * (y - b) * (y0 - b)) |
| / (2.0 * ((x0 - x) * (y - b) - (x - a) * (y0 - y))); |
| double cy = ((x0 - x) * (x - a) * (x0 - a) / d2 + (x - a) * (y0 - y) |
| * (y0 + y) - (x0 - x) * (y - b) * (y + b)) |
| / (2.0 * ((x - a) * (y0 - y) - (x0 - x) * (y - b))); |
| |
| // calculate radii of ellipse |
| double rx = Math.sqrt(Math.pow(x0 - cx, 2) + Math.pow(y0 - cy, 2) * d2); |
| double ry = rx / d; |
| |
| // Arc2D requires us to draw an arc from one point to another, so we |
| // need to calculate the angle of the start point and end point along |
| // the ellipse |
| // - Derived from parametric form of ellipse: x = h + a*cos(t); y = k + |
| // b*sin(t) |
| |
| double ctrlAngle = Math.toDegrees(Math.atan2((b - cy) / ry, (a - cx) |
| / rx)); |
| double startAngle = Math.toDegrees(Math.atan2((y0 - cy) / ry, (x0 - cx) |
| / rx)); |
| double endAngle = Math.toDegrees(Math.atan2((y - cy) / ry, (x - cx) |
| / rx)); |
| |
| double sweep = computeSweep(startAngle, endAngle, ctrlAngle); |
| |
| // Now we have enough information to go on. Create the arc. |
| Arc2D arc = new Arc2D.Double(cx - rx, cy - ry, rx * 2, ry * 2, |
| -startAngle, sweep, Arc2D.OPEN); |
| |
| // rotate the arc back to the original coordinate system |
| at.setToRotation(c); |
| path.append(at.createTransformedShape(arc), false); |
| } |
| |
| protected static double computeSweep(double startAngle, double endAngle, |
| double ctrlAngle) { |
| double sweep; |
| |
| startAngle = (360.0 + startAngle) % 360.0; |
| endAngle = (360.0 + endAngle) % 360.0; |
| ctrlAngle = (360.0 + ctrlAngle) % 360.0; |
| |
| // different sweeps depending on where the control point is |
| |
| if (startAngle < endAngle) { |
| if (startAngle < ctrlAngle && ctrlAngle < endAngle) { |
| sweep = startAngle - endAngle; |
| } else { |
| sweep = 360 + (startAngle - endAngle); |
| } |
| } else { |
| if (endAngle < ctrlAngle && ctrlAngle < startAngle) { |
| sweep = startAngle - endAngle; |
| } else { |
| sweep = -(360 - (startAngle - endAngle)); |
| } |
| } |
| |
| return sweep; |
| } |
| } |