Making a line in html with the path element - javascript

I am trying to draw a line between two elements in html/js using svg and path elements and it works but I need a way to draw arcs that are rotated so it is smother. This is my first post here so sorry if it is not easy to understand
I tryed to draw a arc that is rotated to be smooth with a line. image My code is
`
function add_connection(start, end) {
// Goto start - 2 on the y axis
// Draw Arc
// Goto end + 2 on the y axis
// Draw Arc
// Finish
const move_start = "M {SX} {SY}" // SY - 2
const draw_arc = " A 1 1 0 0 0 {SX} {SY} " // SY + 2
const draw_line = "L {EX} {EY}" // EY + 2
// draw_arc
const node = document.createElement("svg")
// Define Variables
var path_html = ""
const start_x = start.left + (start.width/2)
const start_y = start.top + (start.height/2)
const end_x = end.left + (end.width/2)
const end_y = end.top + (end.height/2)
// HTML Start
path_html += "<path d='"
// Path Data
path_html += move_start.replace("{SY}", start_y-2).replace("{SX}", start_x)
path_html += draw_arc.replace("{SY}", start_y+2).replace("{SX}", start_x)
path_html += draw_line.replace("{EY}", end_y+2).replace("{EX}", end_x)
path_html += draw_arc.replace("{SY}", start_y+2).replace("{SX}", start_x)
// HTML End
path_html += " Z'>"
// Add Node
node.innerHTML = path_html
document.getElementById("paths").appendChild(node)
console.log("Created Connection")`
start and end are bounding boxes

Using two line elements you can create the same effect by setting the stroke-linecap and two different stroke widths.
const svg01 = document.getElementById('svg01');
add_connection({left:5,top:5},{left:95,top:25});
function add_connection(start, end) {
let g = document.createElementNS('http://www.w3.org/2000/svg', 'g');
let line1 = document.createElementNS('http://www.w3.org/2000/svg', 'line');
let line2 = document.createElementNS('http://www.w3.org/2000/svg', 'line');
line1.setAttribute('stroke', 'black');
line2.setAttribute('stroke', 'yellow');
line1.setAttribute('stroke-width', '5');
line2.setAttribute('stroke-width', '3');
line1.setAttribute('stroke-linecap', 'round');
line2.setAttribute('stroke-linecap', 'round');
line1.setAttribute('x1', start.left);
line1.setAttribute('y1', start.top);
line1.setAttribute('x2', end.left);
line1.setAttribute('y2', end.top);
line2.setAttribute('x1', start.left);
line2.setAttribute('y1', start.top);
line2.setAttribute('x2', end.left);
line2.setAttribute('y2', end.top);
g.appendChild(line1);
g.appendChild(line2);
svg01.appendChild(g);
}
<svg id="svg01" viewBox="0 0 100 30" xmlns="http://www.w3.org/2000/svg">
</svg>

Related

Javascript 10 lines on a canvas on click tell which one was clicked

I have to draw on a canvas 10 random lines with 10 random color. If I click on one line I have print out the order of the line and the color of the line. How to solve this?
Problem representation image
E1: Another question is, that how can I get the canvas color on a specific X,Y coordinate?
To draw the lines with 10 random positions and colours, use Math.random();, and of course the <canvas> element.
And to get the cursor position, use the element.clientX and element.clientY proprieties.
HTML:
<canvas id="_canvas"></canvas>
And the JavaScript code will do all the job, read the comments:
var element = document.getElementById('_canvas'); //Get the element
var ctx = element.getContext("2d"); //...And its context...
var cols = []; //Whoah! The array where the colors will be stored in.
let width = element.offsetWidth; //Get the width of the canvas,
let height = element.offsetHeight; //And the height!
for(i = 0; i < 10; i++) {
let r = Math.floor(Math.random() * 255); //Get a random color for the line, the red value,
let g = Math.floor(Math.random() * 255); //Green,
let b = Math.floor(Math.random() * 255); //And blue.
r = parseInt(r); //Remove decimal numbers.
g = parseInt(g);
b = parseInt(b);
var rgbToHex = function (rgb) { //And this function converts RGB to HEX!
var hex = Number(rgb).toString(16);
if (hex.length < 2) {
hex = "0" + hex;
}
return hex;
};
var fullColorHex = function(r,g,b) { //I mean this function converts RGB to HEX, but... anyway...
var red = rgbToHex(r);
var green = rgbToHex(g);
var blue = rgbToHex(b);
return red+green+blue;
};
let hexval = "#" + fullColorHex(r, g, b); //Now convert our RGB value to a hexadecimal number and add a "sharp" to the beginning of it!
ctx.beginPath(); //Alright now the serious stuff, begin painting.
ctx.moveTo(Math.random() * (+width - +0) + +0, Math.random() * (+height - +0) + +0); //From a random position,
ctx.lineTo(Math.random() * (+width - +0) + +0, Math.random() * (+height - +0) + +0); //To a random position!
ctx.strokeStyle = hexval; //change the line's color.
ctx.lineWidth = 8; //Change the strength of the lines, they will look insane but you will never click on them without this.
cols.push(hexval.replace("#", ""));
ctx.stroke(); //Draw it!
}
document.onclick = function(e) {
if(e.target == element) { //Does the user click on the canvas?
var x = e.clientX; //Get the mouse position
var y = e.clientY;
var c = element.getContext('2d');
var p = c.getImageData(x, y, 1, 1).data;
var hex = "#" + ("000000" + fullColorHex(p[0], p[1], p[2])).slice(-6); //Get the color of the pixel!
for(i = 0; i < 10; i++) {
console.log("Colors: " + cols[i] + ", hex: " + hex);
if("#" + cols[i] == hex) {
alert("Order: " + i + ", color: " + hex); //Done!
}
}
}
};
CodePen: https://codepen.io/marchmello/pen/MWwMyog?editors=0010

How can i get the new X and Y position after rotating?

I need to make a Sunflowerplot and after rotating the lines the position of them has to be translated back. But i don't know how to get the new x,y pos after rotating.
I want to rotate only the line but its position does ofcourse change too.
var xOld = (save[i][0])/(xS.value/100/3.4);
var yOld = (save[i][1])/(yS.value/100/3.5*-1);
//Above code is to get and transform the position where to draw
//and works very well without rotate
var line = d3.select("svg")
.append("line")
.attr("stroke-width","1")
//Backwardline
.attr("x1",xOld-lineLength)
.attr("y1",yOld)
//I think that i need to translate the new position here
.attr("transform", "translate(50, " + 360 +") rotate(" + 45 * -1+ ")")
//Forwardline
.attr("x2",(xOld)+lineLength)
.attr("y2",(yOld))
.style("stroke","blue");
I added a snippet where you can determine the number of petals yourself, and play with the styling and rotation a little if you want
const svg = d3.select('body').append('svg');
// The distance in pixels between the edge and the center of each petal
const petalRadius = 20;
// sin, cos, and tan work in radians
const fullCircle = 2 * Math.PI;
// Zero rads make the shape point to the right with the right angle
// Use - 0.5 * pi rads to make the first petal point upwards instead
// You can play with this offset to see what it does
const offset = - Math.PI / 2;
function drawSunflower(container, petals) {
const radsPerPetal = fullCircle / petals;
const path = container.append('path');
// We're going to need this a lot. M moves to the given coordinates, in this case
// That is the center of the sunflower
const goToCenter = ` M ${petalRadius},${petalRadius}`;
// Construct the `d` attribute. Start in the center and work form there.
let d = goToCenter;
let counter = 0;
while (counter < petals) {
const rads = counter * radsPerPetal + offset;
const dx = Math.cos(rads) * petalRadius;
const dy = Math.sin(rads) * petalRadius;
// Draw a relative line to dx, dy, then go to center
d += `l ${dx},${dy}` + goToCenter;
counter += 1;
}
path.attr('d', d);
}
const transform = 2 * petalRadius + 5;
for (let i = 0; i < 5; i++) {
for (let j = 0; j < 3; j++) {
let container = svg.append('g').attr('transform', `translate(${i * transform}, ${j * transform})`);
drawSunflower(container, i * 5 + j + 1);
}
}
g > path {
stroke: black;
stroke-width: 1px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>

Calc/Place horizontal labels around SVG Donut Graph (NO LIBRARIES)

I am looking to add 'floating' labels around an SVG Donut Graph, as seen below, using only SVG elements and vanilla JS.
The corresponding lines are a plus, but not necessary.
Building of the dynamic donut/sectors is complete and I have it ported/working correctly within an Angular application. I am using vanilla JS w/Jquery here for ease.
I have scoured StackOverflow and Google to their depths but cannot locate a Question & Answer which provides a compatible solution using an algorithm/code to calculate desired placement. 90% of similar questions reference d3.js while the others reference a different charting library.
Not sure where to begin as I don't think the information I have on the segments is enough to calc placement within the viewBox.
I do know the circumference of the drawn circles are each 100 with a radius of 15.91549430918954. stroke-dasharray and stroke-dashoffset are calculated and set to each segment to build out the colored segments appropriately.
Is it possible to use what data I have to figure this out? Do I need a little more? How would I translate the calculation to x/y coordinates for the viewBox?
Thanks.
$(document).ready(function() {
let data = [500,100,350,600];
let dataTotal = 0;
for (let i = 0; i < data.length; i++) {
dataTotal += data[i];
}
let colors = ['#ce4b99', '#4286f4', '#62f441', '#952ec1'];
let labels = ['A', 'B', 'C', 'D'];
let total = 0;
for (let i = 0; i < data.length; i++) {
let dataPercent = data[i] / dataTotal;
let dataVal = 100 * dataPercent;
var chart = document.getElementById("chart");
const group = document.createElementNS('http://www.w3.org/2000/svg', 'g');
const node = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
const label = document.createElementNS('http://www.w3.org/2000/svg', 'text');
chart.appendChild(group);
group.appendChild(node);
group.appendChild(label);
label.textContent = labels[i];
label.setAttribute('class', 'data-label');
label.setAttribute('x', '20%');
label.setAttribute('y', '20%');
node.setAttribute('stroke-dasharray',`${dataVal} ${100 - dataVal}`);
node.setAttribute('class','donut-segment');
node.setAttribute('fill','transparent');
node.setAttribute('stroke-dashoffset', getOffset(total, i))
total += dataVal;
node.setAttribute('stroke', colors[i]);
node.setAttribute('stroke-width','3');
node.setAttribute('r','15.91549430918954');
node.setAttribute('cx','42');
node.setAttribute('cy','42');
}
});
function getOffset(total, i) {
if (i === 0) return 25;
return ((100 - total) + 25);
}
.chart-contain {
width: 50%;
margin 0 auto;
}
.data-label {
font-size: 4px;
}
<script src='https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js'></script>
<div class='chart-contain'>
<svg id='chart' width="100%" height="100%" viewBox="0 0 84 84" class="donut">
<circle class="donut-hole" cx="42" cy="42" r="15.91549430918954" fill="transparent"></circle>
<circle class="donut-ring" cx="42" cy="42" r="15.91549430918954" fill="transparent" stroke="#d2d3d4" stroke-width="3">
</circle>
</svg>
</div>
You may not like my answer because I couldn't use your code. I needed to organise the data differently. However I keep the general idea: I'm using stroke-dasharray and stroke-dashoffset. I would have used paths instead of strokes.
I'm using a radius of 20, but you may change it back. You can also change it to anything else.
In order to draw the text I calculated the starting angle and the ending angle of your arcs. Then I calculated the middle angle. Once I know the middle angle I can draw the text and the lines.
I'm putting the arcs in the #chart group, the text in the #text group and the lines in the #lines group.
const SVG_NS = 'http://www.w3.org/2000/svg';
const XLINK = 'http://www.w3.org/1999/xlink';
let r = 20,cx=42,cy=42;// the attributes for the circle
let text_r = r + 10; // the radius for the text
let perimeter = 2*r*Math.PI;//the perimeter of the circle
donut.setAttributeNS(null, "r", r);
donut.setAttributeNS(null, "cx", cx);
donut.setAttributeNS(null, "cy", cy);
let data = [
{
value:500,
stroke:'#ce4b99',
label: 'A'
},
{
value:100,
stroke:'#4286f4',
label: 'B'
},
{
value:350,
stroke:'#62f441',
label: 'C'
},
{
value:600,
stroke:'#952ec1',
label: 'D'
}
]
let total = 0;
data.map(d =>{
d.temp = total;
total += d.value;
})
data.map(d =>{
d.offset = map(d.temp,0,total,0, perimeter)
d.real_value = map(d.value, 0, total, 0, perimeter);
d.dashArray = `${d.real_value},${perimeter}`;
/// angles
let angleStart = -d.offset/r;
let angleEnd = (d.offset + d.real_value)/r;
d.angleMiddle = (angleEnd - angleStart)/2;
// text
let t = {}
t.props ={
x : cx + text_r*Math.cos(d.angleMiddle),
y : cy + text_r*Math.sin(d.angleMiddle),
}
t.textContent = d.label;
d.text_point = t;
//line
let l = {
x1 : cx + r*Math.cos(d.angleMiddle),
y1 : cy + r*Math.sin(d.angleMiddle),
x2 : cx + .9*text_r*Math.cos(d.angleMiddle),
y2 : cy + .9*text_r*Math.sin(d.angleMiddle),
}
d.line = l;
})
data.map(d=>{// create a new use element
d.use = document.createElementNS(SVG_NS, 'use');
d.use.setAttributeNS(XLINK, 'xlink:href', '#donut');
d.use.setAttributeNS(null, 'stroke', d.stroke);
d.use.setAttributeNS(null, 'stroke-dasharray', d.dashArray);
d.use.setAttributeNS(null, 'stroke-dashoffset', -d.offset);
chart.appendChild(d.use);
drawLine(d.line, lines);
drawText(d.text_point, text);
})
// helpers
function drawLine(o, parent) {
var line = document.createElementNS(SVG_NS, 'line');
for (var name in o) {
if (o.hasOwnProperty(name)) {
line.setAttributeNS(null, name, o[name]);
}
}
parent.appendChild(line);
return line;
}
function drawText(o, parent) {
var text = document.createElementNS(SVG_NS, 'text');
for (var name in o.props) {
if (o.props.hasOwnProperty(name)) {
text.setAttributeNS(null, name, o.props[name]);
}
text.textContent = o.textContent;
}
parent.appendChild(text);
return text;
}
function map(n, a, b, _a, _b) {
let d = b - a;
let _d = _b - _a;
let u = _d / d;
return _a + n * u;
}
svg{border:1px solid;}
#donut{fill:none; stroke-width:5px;}
text{dominant-baseline:central;text-anchor:middle;font-size: 4px;}
line{stroke:black;stroke-width:.1px}
<div class='chart-contain'>
<svg viewBox="0 0 84 84" width="250" class="donut">
<defs>
<circle id="donut" cx="42" cy="42" r="20" ></circle>
</defs>
<g id='text'></g>
<g id='lines'></g>
<g id='chart'></g>
</svg>
</div>
In order to get it exactly like yours you may want to rotate the whole chart -Math.PI/2 and then rotate the text back.

How to generate a Polygon with N sides (equal length and angles)

I am looking for a way to calculate the SVG path for a regular polygon given the number of sides, the width and height of the wrapper-container.
Any help will be really appreciated!
Thank you,
Best.
Update: N could be both even and odd.
Step 1. How to create SVG
const $svg = document.createElementNS("http://www.w3.org/2000/svg", "svg")
$svg.setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns:xlink", "http://www.w3.org/1999/xlink")
$svg.setAttribute('width', 500)
$svg.setAttribute('height', 500)
$svg.setAttribute('viewBox', '0 0 500 500')
document.body.appendChild($svg)
Step 2. How to create Line
const $el= document.createElementNS("http://www.w3.org/2000/svg", "line")
$el.setAttribute('x1', 0)
$el.setAttribute('y1', 0)
$el.setAttribute('x2', 100) // not real
$el.setAttribute('y2', 100) // not real
$svg.appendChild($el)
Step 3. How to create real Line
function (sides, width) {
const angle = 360 / sides
const $el1= document.createElementNS("http://www.w3.org/2000/svg", "line")
$el1.setAttribute('x1', 0)
$el1.setAttribute('y1', 0)
$el1.setAttribute('x2', width)
$el1.setAttribute('y2', 0)
$svg.appendChild($el1)
// we already created first line, so start from 1
for (var i = 1; i < sides.length; i++) {
const $el= document.createElementNS("http://www.w3.org/2000/svg", "line")
const offsetX_angle = 180 - angle
const hypotenuse = width / Math.sin(90)
const offsetX = Math.sin(90 - offsetX_angle) * hypotenuse
const offsetY = Math.sin(offsetX_angle) * hypotenuse
// now we know the offsets
// and we can easily populate other lines using same offset
const x1 = width
const y1 = 0
const x2 = width + offsetX
const y2 = 0 + offsetY
// just need to switch angles based in i
}
}
This is not full solution, it's steps for making solution

build specific icon from data

I want to display a specific icon in different boxes using d3.
I have a data as an array of [x0, y0, x1, y1, vx, vy] where:
x0, y0 is the first corner of a box,
x1, y1 the second corner and
vx, vy two parameters (velocity)
that will be used to generate a SVG path.
I am using:
var boxes = nodeSVGGroup.selectAll("rect").data(nodes).enter().append("rect");
to generate my boxes and this is working well
My problem comes when I want to create the SVG path (icon) and properly render it in each box (it needs to be generated, translated and rotated to fit the center of each box.
I am using a similar pattern i.e.
var selection = nodeSVGGroup.selectAll(".barb").data(nodes);
selection.enter()
.append('g')
.each(function(node, i) {
var vx = node[4];
var vy = node[5];
var speed = Math.sqrt(vx*vx + vy*vy);
var angle = Math.atan2(vx, vy);
// generate a path based on speed. it looks like
// M2 L8 2 M0 0 L1 2 Z
var path = ...
var scale = 0.5*(node[1]-node[0])/8;
var g = d3.select(this).data([path,angle,scale]).enter().append('g');
// still need to add following transforms
// translate(' + node[2] + ', ' + node[3] + ')
// scale(' + scale + ')
// rotate(' + angle + ' ' + 0 + ' ' + 0 + ')
// translate(-8, -2)',
g.append('path').attr('d', function(d){console.log(d); return d[0];})
.attr('vector-effect', 'non-scaling-stroke');
})
.attr('class', 'wind-arrow');
I get the error Uncaught TypeError: Cannot read property 'ownerDocument' of null(…) which seems to be related to this line
.each(function(node, i) {
What am I doing wrong?
The full code is here
It's not entirely clear why are you trying to create an "enter" selection inside an each. I'm not sure if I understand your goals, but you can simply use d3.select(this) to append the path to each group:
d3.select(this).append('path')
.attr('d', path)
Here is the updated fiddle: https://jsfiddle.net/9x169eL1/

Categories

Resources