SVG specification中的ARCTO与Canvas中的ARCTO完全不同。我有一个用例,我将按照SVG规范获取数据,但我需要在Canvas上绘制它。
我尝试了这个,但我想我的几何体很弱。你能帮忙吗?
答案 0 :(得分:2)
svg ellipse和canvas arc之间的区别在于svg中有2个半径,arcTo中只有1个半径。然后,您还需要在画布中以特定角度旋转圆弧。要模拟2个半径,您需要使用给定坐标具有最小半径的圆弧。然后,您需要使用系数(rx / ry)在特定方向上缩放此弧。而现在你只需要旋转。但是在这种方法中很难说明你要显示的椭圆的哪个部分,因为它取决于svg规范中的大弧标志和扫描标志。另一个问题是通过结束坐标(来自svg规范)限制弧。因此,通过arcTo,你可以建立一个椭圆的最大值的一半。
如果椭圆上有3个控制点的坐标,也可以使用bezierCurveTo(x0,y0,x1,y1,x2,y2)绘制椭圆的一部分。使用这种方法,您可以构建椭圆的任何部分。当然,对于超过PI的段,您将需要至少两条曲线
根据SVG规范(rx ry x-axis-rotation大弧标志扫描标志x y)。所以样本路径就是这样:
M100,100 a25,50 -30 0,1 50,-25
Here你可能会发现应该如何画出贝塞尔曲线。
现在您有上下文点(100,100)和结束点(100 + 50,100-25) 您需要在旋转到-30度之前计算控制点。
这是一个适合我的例子:
$(document).ready(function(){
var startX = 100;
var startY = 100;
var dX = 50;
var dY = -25;
var angle = -30;
var rx = 25;
var ry = 50;
var svg = Raphael($('#svg')[0], 200, 200);
var path = "M" +startX + "," + startY + " a" + rx + "," + ry + " " + angle + " 0,1" + " " + dX + "," +dY;
svg.path(path).attr({"stroke-width" : 2, "stroke" : "#FFFFFF"});
var kappa = .5522848,
ox = rx*kappa,
oy = ry*kappa,
xm = startX + rx, // x-middle
ym = startY + ry; // y-middle
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
ctx.moveTo(startX,startY);
ctx.bezierCurveTo(startX, startY - oy, startX + ox, startY - ry, startX + rx, startY - ry);
ctx.bezierCurveTo(startX + rx + ox, startY - ry, startX + 2*rx, startY - oy, startX + dX, startY + dY);
ctx.stroke();
});
标记很简单:
<div id="svg" style="border: 1px solid black;position : absolute;top : 50px;left : 50px;"></div>
<canvas id="canvas" width="200px" height="200px" style="border: 1px solid black;position : absolute;top : 300px;left : 50px;"></canvas>
曲线不相似,因为我没有将控制点旋转到-30度。但我相信这是你唯一需要做的事情。因为如果你将角度= 0,它们将是相似的 您可以使用this文章来获取轮换数学。
PS:我从this回答
中获取了部分代码答案 1 :(得分:0)
当试图将“M100,100 a25,50 -30 0,1 50,-25”映射到画布时使用我的功能。 当然,我在脑海中写下了这一点。
椭圆(100,100,50,-25,50,FALSE);
function ellipse(x1, y1, x2, y2, radius, clockwise) {
var cBx = (x1 + x2) / 2; //get point between xy1 and xy2
var cBy = (y1 + y2) / 2;
var aB = Math.atan2(y1 - y2, x1 - x2); //get angle to bulge point in radians
if (clockwise) { aB += (90 * (Math.PI / 180)); }
else { aB -= (90 * (Math.PI / 180)); }
var op_side = Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2)) / 2;
var adj_side = Math.sqrt(Math.pow(radius, 2) - Math.pow(op_side, 2));
if (isNaN(adj_side)) {
adj_side = Math.sqrt(Math.pow(op_side, 2) - Math.pow(radius, 2));
}
var Cx = cBx + (adj_side * Math.cos(aB));
var Cy = cBy + (adj_side * Math.sin(aB));
var startA = Math.atan2(y1 - Cy, x1 - Cx); //get start/end angles in radians
var endA = Math.atan2(y2 - Cy, x2 - Cx);
var mid = (startA + endA) / 2;
var Mx = Cx + (radius * Math.cos(mid));
var My = Cy + (radius * Math.sin(mid));
context.arc(Cx, Cy, radius, startA, endA, clockwise);
}
答案 2 :(得分:0)
以下代码段是从Gabe Lerner全面的CANVG包的相关部分中提取的(参见https://github.com/canvg/canvg),对于那些喜欢我的人,可能不想要整个9码的Gabe包。与早期的解决方案不同,它不是近似值,它与SVG弧路径元素完全等效,我非常感谢Gabe。
还有一点是,如果您在绘制路径之前已经对画布应用了一些缩放和/或平移,则需要将此因素分解为对Context.translate的两次调用的参数以及Context.arc
调用的radius参数function drawSVGarcOnCanvas (Context,lastX,lastY,rx,ry,xAxisRotation,largeArcFlag,sweepFlag,x,y)
{
//--------------------
// rx, ry, xAxisRotation, largeArcFlag, sweepFlag, x, y
// are the 6 data items in the SVG path declaration following the A
//
// lastX and lastY are the previous point on the path before the arc
//--------------------
// useful functions
var m = function ( v) {return Math.sqrt (Math.pow (v[0],2) + Math.pow (v[1],2))};
var r = function (u, v) {return ( u[0]*v[0] + u[1]*v[1]) / (m(u) * m(v))};
var ang = function (u, v) {return ((u[0]*v[1] < u[1]*v[0])? -1 : 1) * Math.acos (r (u,v))};
//--------------------
var currpX = Math.cos (xAxisRotation) * (lastX - x) / 2.0 + Math.sin (xAxisRotation) * (lastY - y) / 2.0 ;
var currpY = -Math.sin (xAxisRotation) * (lastX - x) / 2.0 + Math.cos (xAxisRotation) * (lastY - y) / 2.0 ;
var l = Math.pow (currpX,2) / Math.pow (rx,2) + Math.pow (currpY,2) / Math.pow (ry,2);
if (l > 1) {rx *= Math.sqrt (l); ry *= Math.sqrt (l)};
var s = ((largeArcFlag == sweepFlag)? -1 : 1) * Math.sqrt
(( (Math.pow (rx,2) * Math.pow (ry ,2)) - (Math.pow (rx,2) * Math.pow (currpY,2)) - (Math.pow (ry,2) * Math.pow (currpX,2)))
/ (Math.pow (rx,2) * Math.pow (currpY,2) + Math.pow (ry,2) * Math.pow (currpX,2)));
if (isNaN (s)) s = 0 ;
var cppX = s * rx * currpY / ry ;
var cppY = s * -ry * currpX / rx ;
var centpX = (lastX + x) / 2.0 + Math.cos (xAxisRotation) * cppX - Math.sin (xAxisRotation) * cppY ;
var centpY = (lastY + y) / 2.0 + Math.sin (xAxisRotation) * cppX + Math.cos (xAxisRotation) * cppY ;
var ang1 = ang ([1,0], [(currpX-cppX)/rx,(currpY-cppY)/ry]);
var a = [( currpX-cppX)/rx,(currpY-cppY)/ry];
var b = [(-currpX-cppX)/rx,(-currpY-cppY)/ry];
var angd = ang (a,b);
if (r (a,b) <= -1) angd = Math.PI;
if (r (a,b) >= 1) angd = 0;
var rad = (rx > ry)? rx : ry;
var sx = (rx > ry)? 1 : rx / ry;
var sy = (rx > ry)? ry / rx : 1;
Context.translate (centpX,centpY);
Context.rotate (xAxisRotation);
Context.scale (sx, sy);
Context.arc (0, 0, rad, ang1, ang1 + angd, 1 - sweepFlag);
Context.scale (1/sx, 1/sy);
Context.rotate (-xAxisRotation);
Context.translate (-centpX, -centpY);
};
答案 3 :(得分:0)
我遇到了同样的问题,所以我碰到了这篇文章。 Implementation Requirements Appendix of the W3C SVG definition确切说明了如何将表单(他们称为)端点参数化转换为中心参数化并返回:
SVG弧(端点参数化)描述为:
x
和y
值)画布弧使用(中心点参数化):
这意味着从SVG转换为画布,您可以使用以下方程式(直接从W3C的给定网址获取):
计算
(x1′, y1′)
(方程F.6.5.1)计算
(cx′, cy′)
(方程F.6.5.2)如果f A ≠f S ,则选择+号,而- 如果f A = f S ,则选择符号。
从
(cx, cy)
计算(cx′, cy′)
(方程F.6.5.3)计算θ 1 和Δθ(方程F.6.5.5和F.6.5.6)
编辑:我现在正在使用其他方程式,请看底部
其中θ 1 在−360°<Δθ<360°范围内固定为:
如果f S = 0,则Δθ<0,
否则,如果f S = 1,则Δθ> 0。
换句话说,如果f S = 0且位于(F.6.5.6)的右侧 大于0,则减去360°,而如果f S = 1 并且(F.6.5.6)的右侧小于0,然后加360°。在所有 其他情况保持不变。
版权所有©2011年8月16日,万维网联盟(麻省理工学院,ERCIM,京王,北航)。 http://www.w3.org/Consortium/Legal/2015/doc-license
我现在使用以下公式确定θ 1 和Δθ:
这只是圆弧的起点和终点与中心点之间的向量。由于在旋转之前已计算了角度,因此减去了φ。您可以根据需要将其保留。
我收到给定方程式的错误结果,但这也可能是实现过程中的错误。当试图查找错误时,我在想W3C在这里做什么。我一直在寻找如何计算角度,这是我首先想到的。这为我带来了正确的结果。
当转换回W3C方程时,我也遇到了问题。这可能是因为更改了角度。为了从Canvas转换为SVG,您需要将起始角度和终止角度(θ 1 和θ 2 =θ 1 +Δθ)转换为圆弧交点的中心点。这些是SVG弧的起点和终点。
答案 4 :(得分:0)
有些晚了,但是这里有一个解决方案,可以绘制楔形图,例如饼图。该解决方案使用该线程的所有输入。所以我想我会分享。请注意,它不执行半径检查。...
function toContext(x1, y1, path) {
let fvals = path.split(',').map(item => parseFloat(item));
const rx = fvals[0];
const ry = fvals[1];
const phi = toRad(fvals[2]);
const fA = fvals[3];
const fS = fvals[4];
const x2 = fvals[5];
const y2 = fvals[6];
const cosPhi = Math.cos(phi);
const sinPhi = Math.sin(phi);
const dx = (x1 - x2) / 2.0;
const dy = (y1 - y2) / 2.0;
let A = math.matrix([[cosPhi, sinPhi], [-sinPhi, cosPhi]]);
let B = math.matrix([[dx], [dy]]);
let C = math.multiply(A,B);
const x1_ = C.valueOf()[0][0];
const y1_ = C.valueOf()[1][0];
console.log(`x1_ ${x1_}, y1_ ${y1_} : ${C.valueOf()}`);
// step2
const rx2 = rx*rx;
const ry2 = ry*ry;
const x1_2 = x1_*x1_;
const y1_2 = y1_*y1_;
const g0 = rx2*ry2 - rx2*y1_2 - ry2*x1_2;
const g1 = rx2*y1_2 + ry2*x1_2;
let sq = Math.sqrt(g0/g1);
let sign = (fA === fS) ? -1.0 : 1.0;
sq = sq * sign;
const cx_ = sq * ((rx*y1_)/ry);
const cy_ = sq * -((ry*x1_)/rx);
A = math.matrix([[cosPhi, -sinPhi], [sinPhi, cosPhi]]);
B = math.matrix([[cx_], [cy_]]);
C = math.multiply(A,B);
let cx = C.valueOf()[0][0];
let cy = C.valueOf()[1][0];
cx += ((x1 + x2) / 2.0);
cy += ((y1 + y2) / 2.0);
console.log(`cx: ${cx}, cy: ${cy}`);
const ux = (x1_ - cx_) / rx;
const uy = (y1_ - cy_) / ry;
const vx = (-x1_ - cx_) / rx;
const vy = (-y1_ - cy_) / ry;
let n = Math.sqrt((ux*ux) + (uy*uy));
let p = ux;
sign = (uy < 0) ? -1.0 : 1.0;
let sa = 180.0 *(sign * Math.acos(p/n)) / Math.PI;
n = Math.sqrt((ux*ux + uy*uy) * (vx*vx + vy*vy));
p = ux*vx + uy*vy;
sign = (ux*vy - uy*vx < 0) ? -1.0 : 1.0;
let ea = 180.0 *(sign * Math.acos(p/n)) / Math.PI;
if( !fS && ea > 0 ){
ea -= 360.0;
} else if( fS && ea < 0) {
ea += 360.0;
}
sa %= 360.0;
ea %= 360.0;
sa = toRad(sa);
ea = toRad(ea);
const clockWise = 1 - fS;
return {x1, y1, x2, y2, cx, cy, sa, ea, phi, rx, ry, clockWise}
}