Креслення стрілочки, з позначенням деяких змінних
Поточна ситуація така, що на запит “як намалювати стрілочку”, Google видає купу порад дівчатам про те як зашпаклювати лице. Але проблема трапляється часто, і не тільки в SVG, ось наприклад старий пост про те як малювати вектори в OpenGL, для програмки що проводить структурний аналіз кінематики машин і механізмів. Тому треба виправити цю ситуацію, і написати ще пару публікацій про малювання стрілочок. :)
Тут буде код який було весело писати, і яким варто поділитись. Присутній також JsFiddle. Код дозволяє малювати стрілочки наступного вигляду:
Написано з використанням D3.js, але код можна причепити де завгодно, так як головне тут – функція arrow_path
, яка генерує значення атрибуту d
для тега path
. Приймає вона координати початку і кінця стрілки, ширину лінії стрілки, радіус (задає розмір трикутника на кінці стрілки, і радіус gizmo (пімпочки на середині)). directed
– булевий аргумент, що вказує чи малювати стрілочку на кінці лінії взагалі. gizmo
– якщо false – пімпочки не буде, 'circle'
– буде коло, 'diamond'
– буде ромбик.
Думаю тут можна було б ще зекономити на ручному перетворенні систем координат, бо виходить забагато арифметики. Натомість використати translate
, але щось зразу не додумався. Правда воно і так не тормозить, навіть коли малює отаке чудо:
Сучасні браузери – потужні машини!
var panel = d3.select('body');
svg = panel.append('svg')
.attr('width', 500)
.attr('height', 500);
var arrow_path = function(x1, y1, x2, y2, width, r, directed, gizmo) {
var dx = x2 - x1; // direction of arrow
var dy = y2 - y1;
var l = Math.sqrt(dx * dx + dy * dy); // length of arrow
var fx = dx / l; // forward vector
var fy = dy / l;
var lx = -fy; // side vector
var ly = fx;
var line_rectangle = [
(x1 + lx*width) + ',' + (y1 + ly*width),
(x2 + lx*width) + ',' + (y2 + ly*width),
(x2 - lx*width) + ',' + (y2 - ly*width),
(x1 - lx*width) + ',' + (y1 - ly*width)
];
var alx, aly, arx, ary;
if (directed) {
alx = x2 - fx*r*2 + lx*r;
aly = y2 - fy*r*2 + ly*r;
arx = x2 - fx*r*2 - lx*r;
ary = y2 - fy*r*2 - ly*r;
};
var get_end_points = function () {
// return list of end vertexes that for an arrow or just side of rectangle.
if(directed) {
return [
'L' + (x2 - fx*r*2 + lx * width) + ',' + (y2 - fy*r*2 + ly * width),
'L' + (x2 - fx*r*2 + lx * r) + ',' + (y2 - fy*r*2 + ly * r),
'L' + x2 + ',' + y2,
'L' + (x2 - fx*r*2 - lx * r) + ',' + (y2 - fy*r*2 - ly * r),
'L' + (x2 - fx*r*2 - lx * width) + ',' + (y2 - fy*r*2 - ly * width),
];
} else {
return [
'L' + line_rectangle[1],
'L' + line_rectangle[2],
];
};
};
if (!gizmo) {
return [
'M' + x1 + ',' + y1,
'L' + line_rectangle[0]
].concat(
get_end_points(),
[
'L' + line_rectangle[3],
'L' + x1 + ',' + y1,
]
).join(' ');
};
var cx = (x1 + x2) / 2;
var cy = (y1 + y2) / 2;
var h = Math.sqrt(r*r - width*width);
var arc_rectangle = [
(cx - fx*h + lx*width) + ',' + (cy - fy*h + ly*width),
(cx + fx*h + lx*width) + ',' + (cy + fy*h + ly*width),
(cx + fx*h - lx*width) + ',' + (cy + fy*h - ly*width),
(cx - fx*h - lx*width) + ',' + (cy - fy*h - ly*width),
];
if (gizmo === 'circle') {
return [
'M' + x1 + ',' + y1,
'L' + line_rectangle[0],
'L' + arc_rectangle[0],
'A' + r + ',' + r + ' 0 0,0 ' + arc_rectangle[1],
].concat(
get_end_points(),
[
'L' + arc_rectangle[2],
'A' + r + ',' + r + ' 0 0,0 ' + arc_rectangle[3],
'L' + line_rectangle[3],
'L' + x1 + ',' + y1,
]
).join(' ');
};
if (gizmo === 'diamond') {
return [
'M' + x1 + ',' + y1,
'L' + line_rectangle[0],
'L' + arc_rectangle[0],
'L' + (cx + lx * r) + ',' + (cy + ly*r),
'L' + arc_rectangle[1],
].concat(
get_end_points(),
[
'L' + arc_rectangle[2],
'L' + (cx - lx * r) + ',' + (cy - ly*r),
'L' + arc_rectangle[3],
'L' + line_rectangle[3],
'L' + x1 + ',' + y1,
]
).join(' ');
};
throw 'Unknown gizmo value'
};
svg.append('path')
.attr("d", arrow_path(0, 100, 200, 300, 2, 10, false, 'circle'));
svg.append('path')
.attr("d", arrow_path(50, 100, 250, 300, 2, 10, false, false));
svg.append('path')
.attr("d", arrow_path(100, 100, 300, 300, 2, 10, true, false));
svg.append('path')
.attr("d", arrow_path(150, 100, 350, 300, 2, 10, true, 'circle'));
svg.append('path')
.attr("d", arrow_path(200, 100, 400, 300, 2, 10, true, 'diamond'));
В кінцевому результаті виходить подібний SVG:
<path d="M150,100 L148.5857864376269,101.41421356237309 L241.6575832073514,194.4860103320976 A10,10 0 0,0 255.51398966790242,208.3424167926486 L334.44365081389594,287.27207793864216 L328.7867965644036,292.9289321881345 L350,300 L342.9289321881345,278.7867965644036 L337.27207793864216,284.44365081389594 L258.34241679264863,205.5139896679024 A10,10 0 0,0 244.4860103320976,191.6575832073514 L151.4142135623731,98.58578643762691 L150,100">
<path d="M200,100 L199.29289321881345,100.70710678118655 L292.2572695790783,193.6714831414514 L292.9289321881345,207.07106781186548 L306.3285168585486,207.7427304209217 L385.1507575950825,286.5649711574556 L378.7867965644036,292.9289321881345 L400,300 L392.9289321881345,278.7867965644036 L386.5649711574556,285.1507575950825 L307.7427304209217,206.3285168585486 L307.0710678118655,192.92893218813452 L293.6714831414514,192.2572695790783 L200.70710678118655,99.29289321881345 L200,100">
Страшненько, в порівнянні з line
тому добре що його можна не руками писати.
P.S. Є ще простіший спосіб – називається SVG marker. Правда з ним біда – маркер має окремі обробники для всіх подій миші, тому якщо вішати функції на ці події – якщо миша буде над маркером а не над лінією – не спрацює. Інша проблема – маркер не змінює колір коли змінювати колір лінії. Цей код уникає цих двох проблем. Але якщо вас події і кольори не цікавлять – користуйтесь маркерами.
Filed under:
Графіка,
Кодерство Tagged:
графіка,
JavaScript,
linux