Блоґ одного кібера

Історія хвороби контуженого інформаційним вибухом

Як намалювати стрілочку в SVG

leave a comment »

Креслення стрілочки, з позначенням деяких змінних

Креслення стрілочки, з позначенням деяких змінних

Поточна ситуація така, що на запит “як намалювати стрілочку”, Google видає купу порад дівчатам про те як зашпаклювати лице. Але проблема трапляється часто, і не тільки в SVG, ось наприклад старий пост про те як малювати вектори в OpenGL, для програмки що проводить структурний аналіз кінематики машин і механізмів. Тому треба виправити цю ситуацію, і написати ще пару публікацій про малювання стрілочок.🙂

Тут буде код який було весело писати, і яким варто поділитись. Присутній також JsFiddle. Код дозволяє малювати стрілочки наступного вигляду:

arrows


Написано з використанням D3.js, але код можна причепити де завгодно, так як головне тут – функція arrow_path, яка генерує значення атрибуту d для тега path. Приймає вона координати початку і кінця стрілки, ширину лінії стрілки, радіус (задає розмір трикутника на кінці стрілки, і радіус gizmo (пімпочки на середині)). directed – булевий аргумент, що вказує чи малювати стрілочку на кінці лінії взагалі. gizmo – якщо false – пімпочки не буде, 'circle' – буде коло, 'diamond' – буде ромбик.

Думаю тут можна було б ще зекономити на ручному перетворенні систем координат, бо виходить забагато арифметики. Натомість використати translate, але щось зразу не додумався. Правда воно і так не тормозить, навіть коли малює отаке чудо:

large_map

Сучасні браузери – потужні машини!

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. Правда з ним біда – маркер має окремі обробники для всіх подій миші, тому якщо вішати функції на ці події – якщо миша буде над маркером а не над лінією – не спрацює. Інша проблема – маркер не змінює колір коли змінювати колір лінії. Цей код уникає цих двох проблем. Але якщо вас події і кольори не цікавлять – користуйтесь маркерами.

Written by bunyk

Грудень 1, 2015 at 19:50

Оприлюднено в Графіка, Кодерство

Tagged with , ,

Залишити відповідь

Заповніть поля нижче або авторизуйтесь клікнувши по іконці

Лого WordPress.com

Ви коментуєте, використовуючи свій обліковий запис WordPress.com. Log Out / Змінити )

Twitter picture

Ви коментуєте, використовуючи свій обліковий запис Twitter. Log Out / Змінити )

Facebook photo

Ви коментуєте, використовуючи свій обліковий запис Facebook. Log Out / Змінити )

Google+ photo

Ви коментуєте, використовуючи свій обліковий запис Google+. Log Out / Змінити )

З’єднання з %s

%d блогерам подобається це: