I have an SVG element in Angular 2+ code.
I want to give the user the possibility to print out the following SVG element.
<div class="floor-plan" id="printSectionId2"
(drop)="onDrop($event)"
(dragover)="onDragOverOrEnter($event)"
(dragenter)="onDragOverOrEnter($event)">
<svg id="svgObject" [attr.width]="svgWidth + 'px'" [attr.height]="svgHeight + 'px'">
<ng-container *ngIf="floorPlan && floorPlan.layout">
<rect [attr.x]="floorPlan.x"
[attr.y]="floorPlan.y"
[attr.width]="floorPlan.width"
[attr.height]="floorPlan.height"
class="boundary">
</rect>
<ng-container *ngFor="let t of floorPlan.layout.tables; trackBy: trackByTables">
<g class="dining-table"
[attr.transform]="'translate(' + t.x + ' ' + t.y + ')'"
[class.assigned]="t.isOccupied"
[class.arrival]="t.isArrival"
[class.hasSpecialInformation]="t.hasSpecialInformation"
[class.hasOnlyBreakfast]="t.hasOnlyBreakfast">
<rect *ngIf="t.shape === 'rectangle'"
[attr.width]="t.width"
[attr.height]="t.height"
[attr.transform]="'rotate(' + t.rotation + ' ' + (t.width/2) + ' ' + (t.height/2) + ')'"
[attr.data-tableId]="t.id">
<title>Tisch {{t.number}}</title>
</rect>
<image *ngIf="t.isArrival" xlink:href="http://kommis.net/wp-content/uploads/2018/07/arrival-white.png" x="5" y="25" height="25px" width="25px" opacity="50"></image>
<text *ngIf="t.shape === 'rectangle'"
[attr.x]="t.width * 0.05"
dominant-baseline="hanging"
[attr.data-tableId]="t.id">
<tspan x="5" class="table-number">{{t.number}}</tspan>
<tspan x="5" dy="15" class="guest-info">{{t.guestInfo}}</tspan>
</text>
<ellipse *ngIf="t.shape === 'circle'"
[attr.rx]="t.width / 2"
[attr.ry]="t.height / 2"
[attr.data-tableId]="t.id">
<title>Tisch {{t.number}}</title>
</ellipse>
<text *ngIf="t.shape === 'circle'"
[attr.dy]="t.width / 2 * -0.5"
text-anchor="middle"
[attr.data-tableId]="t.id">
<tspan x="0" class="table-number">{{t.number}}</tspan>
<tspan x="0" dy="15" class="guest-info">{{t.guestInfo}}</tspan>
</text>
</g>
</ng-container>
</ng-container>
</svg>
</div>
Here the CSS:
.floor-plan {
background-color: #eaf3f3;
border-radius: 2px;
}
.floor-plan svg {
/* Is needed because svg is by default inline-block and thus
the wrapping div would get higher by some extra pixels.
See https://stackoverflow.com/questions/24626908/*/
display: block;
}
.floor-plan .boundary {
fill: transparent;
stroke: #0A7A74;
stroke-width: 2px;
}
.dining-table rect,
.dining-table ellipse {
fill: transparent;
stroke: #0A7A74;
stroke-width: 2px;
}
.dining-table.assigned rect,
.dining-table.assigned ellipse {
fill: #0a7a74;
}
.dining-table.assigned tspan {
fill: #ffffff;
}
.dining-table.hasSpecialInformation rect,
.dining-table.hasSpecialInformation ellipse {
stroke: red;
}
.dining-table.hasOnlyBreakfast rect,
.dining-table.hasOnlyBreakfast ellipse {
fill: #0d2f2e;
}
.dining-table image {
opacity: 0.5;
}
tspan.table-number {
fill: #666;
}
tspan.guest-info {
fill: #333;
font-weight: bolder;
}
The element looks like this on the screen:
I wanted to ask, what is the best way to let users print out this SVG graphic?
At the moment I am trying it with the following JS code:
printToCart2(svgObject: string) {
let popupWinindow;
let innerContents = document.getElementById(svgObject).innerHTML;
console.log(innerContents);
popupWinindow = window.open('', '_blank', 'width=1000,height=1000,scrollbars=no,menubar=no,toolbar=no,location=no,status=no,titlebar=no');
popupWinindow.document.open();
popupWinindow.document.write('<html><head><style></style></head><body onload="window.print()">' + innerContents + '</html>');
popupWinindow.document.close();
}
Which gives me, unfortunately, this black square:
Update
The answer given by #KoshVery gives me the desired result:
Update
In order to make the CSS work, I had to add it in the line Popupwindow.document.write() like so:
popupWinindow.document.write('<html><head><style>.floor-plan { background-color: #eaf3f3; border-radius: 2px; } .floor-plan svg { /* Is needed because svg is by default inline-block and thusthe wrapping div would get higher by some extra pixels.See https://stackoverflow.com/questions/24626908/*/ display: block; } .floor-plan .boundary { fill: transparent; stroke: #0A7A74; stroke-width: 2px; } .dining-table rect, .dining-table ellipse { fill: transparent; stroke: #0A7A74; stroke-width: 2px; } .dining-table.assigned rect, .dining-table.assigned ellipse { fill: #0a7a74; } .dining-table.assigned tspan { fill: #ffffff; } .dining-table.hasSpecialInformation rect, .dining-table.hasSpecialInformation ellipse { stroke: red; } .dining-table.hasOnlyBreakfast rect, .dining-table.hasOnlyBreakfast ellipse { fill: #0d2f2e; } .dining-table image { opacity: 0.5; } tspan.table-number { fill: #666; } tspan.guest-info {fill: #333; font-weight: bolder; } </style></head><body onload="window.print()">' + innerContents + '</html>');
Which ends up in the final result:
You have to get outerHTML of your svg:
let innerContents = document.getElementById(svgObject).outerHTML;
OR
If you've got innerHTML wrap it into <svg> here:
popupWinindow.document.write('<html><head><style></style></head><body onload="window.print()"><svg>' + innerContents + '</svg></html>');
Update
You might simplify your print flow like this:
<div>
<svg onclick="window.print(this)" id="svgObject" width="100" height="100">
<circle fill="pink" cx="50" cy="50" r="45"/>
</svg>
</div>
Related
i am going to do some circular charts and now i have full circle with progress bar but dont know why i cannot make it 3/4circle and progress bar.
code:
<!-- The HTML structure for the progress bar -->
<div class="progress-container">
<svg class="progress-bar" width="100%" height="100%" viewBox="0 0 42 42">
<circle class="progress-ring" cx="21" cy="21" r="15.91549430918954" fill="transparent"/>
<circle class="progress-value" cx="21" cy="21" r="15.91549430918954" fill="transparent"/>
</svg>
<div class="progress-text">0%</div>
</div>
/* The CSS styles for the progress bar */
.progress-container {
position: relative;
width: 200px;
height: 200px;
}
.progress-bar {
position: absolute;
top: 0;
left: 0;
}
.progress-ring {
stroke: #ddd;
stroke-width: 2;
fill: transparent;
}
.progress-value {
stroke: #3498db;
stroke-width: 2;
fill: transparent;
stroke-dasharray: 0 100;
transition: stroke-dasharray 0.3s;
}
.progress-text {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 18px;
font-weight: bold;
text-align: center;
color: #3498db;
}
// The JavaScript code to update the progress bar
const progressContainer = document.querySelector('.progress-container');
const progressBar = progressContainer.querySelector('.progress-bar');
const progressValue = progressBar.querySelector('.progress-value');
const progressText = progressContainer.querySelector('.progress-text');
const updateProgress = (value) => {
// Update the value of the progress bar
progressValue.style.strokeDasharray = `${value} 100`;
// Update the text inside the progress bar
progressText.textContent = `${value}%`;
}
updateProgress(50); // Set the progress bar to 50%
This is how it looks like:
but i want it to looks some similar to:
If someone know how to rebuild my code, please help me out.
You would need to draw the 3/4 arc segment with svg path,
<svg class="progress-bar" width="100%" height="100%" viewBox="0 0 42 42">
<!--
<circle class="progress-ring" cx="21" cy="21" r="15.91549430918954" fill="transparent"/>
<circle class="progress-value" cx="21" cy="21" r="15.91549430918954" fill="transparent"/>
-->
<path class="progress-ring" d="M21 21 m-11.254 11.254 a 15.91549430918954 15.91549430918954 135 1 1 22.508 0" fill="transparent" stroke-linecap="round"/>
<path class="progress-value" d="M21 21 m-11.254 11.254 a 15.91549430918954 15.91549430918954 135 1 1 22.508 0" fill="transparent" stroke-linecap="round"/>
</svg>
and adjust the length accordingly.
//progressValue.style.strokeDasharray = `${value} 100`;
progressValue.style.strokeDasharray = `${value * 3 / 4} 75`;
I followed this and made my own svg path scroll. I achieved the animation but I can seem to find that why the dashes are not appearing on the line. I tried different things but still could not find any solution. Please can some one help.
// Get the id of the <path> element and the length of <path>
var myline = document.getElementById("myline");
var length = myline.getTotalLength();
circle = document.getElementById("circle");
// The start position of the drawing
myline.style.strokeDasharray = length;
// Hide the triangle by offsetting dash. Remove this line to show the triangle before scroll draw
myline.style.strokeDashoffset = length;
// Find scroll percentage on scroll (using cross-browser properties), and offset dash same amount as percentage scrolled
window.addEventListener("scroll", myFunction);
function myFunction() {
// What % down is it?
var scrollpercent = (document.body.scrollTop + document.documentElement.scrollTop * 4) / (document.documentElement.scrollHeight - document.documentElement.clientHeight);
// Length to offset the dashes
var draw = length * scrollpercent;
// Reverse the drawing (when scrolling upwards)
myline.style.strokeDashoffset = length - draw;
//get point at length
endPoint = myline.getPointAtLength(draw);
circle.setAttribute("cx", endPoint.x);
circle.setAttribute("cy", endPoint.y);
}
body {
height: 2000px;
background: #f1f1f1;
}
#circle {
fill: #000;
}
#myLine {
stroke-dasharray: 8;
}
#mySVG {
position: relative;
top: 10%;
width: 90vw;
height: 90vh;
margin-left: -50px;
}
.st0 {
fill: none;
stroke-dashoffset: 3px;
stroke: green;
stroke-width: 5;
stroke-miterlimit: 10;
stroke-dasharray: 20;
}
.mask-style {
stroke: white;
stroke-width: 7;
}
<svg id="mySVG" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 420.099 699.491" style="padding-bottom: 0%; overflow: visible;">
<defs>
<mask id="dash-mask">
<path class="st0 mask-style" stroke-dasharray="10,9" d="M5381.266 335.957s-105.8-62.765-170.449 28.765-63.274 160.934 0 354.388 31.274 395.636-229.089 232.727" transform="translate(-4980.932 -313.455)"/>
</mask>
</defs>
<circle id="circle" cx="404" cy="20" r="8"/>
<path id="myline" class="st0" stroke-dasharray="10,9" d="M5381.266 335.957s-105.8-62.765-170.449 28.765-63.274 160.934 0 354.388 31.274 395.636-229.089 232.727" transform="translate(-4980.932 -313.455)" />
</svg>
So what i am trying is to just make the path dashed.
You need to construct the correct stroke-dash-array - right now the CSS specified stroke dasharray (10,9) is being over-written by myline.style.strokeDasharray = length. That needs to be something like "10, 9, 10, 9, 10, 9, 10,9, 10,9" + length - so the part of the line that is being hidden by the offset has an appropriate dash.
The following tweak is not completely correct - you need to write some code to construct the correct dash-array - but it should give you the idea.
// Get the id of the <path> element and the length of <path>
var myline = document.getElementById("myline");
var length = myline.getTotalLength();
circle = document.getElementById("circle");
// The start position of the drawing
myline.style.strokeDasharray = "0, " + length + " ,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9 " + length;
// Hide the triangle by offsetting dash. Remove this line to show the triangle before scroll draw
myline.style.strokeDashoffset = length;
// Find scroll percentage on scroll (using cross-browser properties), and offset dash same amount as percentage scrolled
window.addEventListener("scroll", myFunction);
function myFunction() {
// What % down is it?
var scrollpercent = (document.body.scrollTop + document.documentElement.scrollTop * 4) / (document.documentElement.scrollHeight - document.documentElement.clientHeight);
// Length to offset the dashes
var draw = length * scrollpercent;
// Reverse the drawing (when scrolling upwards)
myline.style.strokeDashoffset = length - draw;
//get point at length
endPoint = myline.getPointAtLength(draw);
circle.setAttribute("cx", endPoint.x);
circle.setAttribute("cy", endPoint.y);
}
body {
height: 2000px;
background: #f1f1f1;
}
#circle {
fill: #000;
}
#myLine {
stroke-dasharray: 8;
}
#mySVG {
position: relative;
top: 10%;
width: 90vw;
height: 90vh;
margin-left: -50px;
}
.st0 {
fill: none;
stroke-dashoffset: 3px;
stroke: green;
stroke-width: 5;
stroke-miterlimit: 10;
stroke-dasharray: 20;
}
.mask-style {
stroke: white;
stroke-width: 7;
}
<svg id="mySVG" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 420.099 699.491" style="padding-bottom: 0%; overflow: visible;">
<defs>
<mask id="dash-mask">
<path class="st0 mask-style" stroke-dasharray="10,9" d="M5381.266 335.957s-105.8-62.765-170.449 28.765-63.274 160.934 0 354.388 31.274 395.636-229.089 232.727" transform="translate(-4980.932 -313.455)"/>
</mask>
</defs>
<circle id="circle" cx="404" cy="20" r="8"/>
<path id="myline" class="st0" stroke-dasharray="10,9" d="M5381.266 335.957s-105.8-62.765-170.449 28.765-63.274 160.934 0 354.388 31.274 395.636-229.089 232.727" transform="translate(-4980.932 -313.455)" />
</svg>
For quite a while I have been dealing with this scenario where I want to zoom in on an SVG image by clicking on it (or tapping in case of a touch-screen device), having it so that where you have clicked stays in the same position on the page while zooming in.
What happens at the moment is that when zooming is done, the pivot is not the clicked point but another point. It seems I cannot get to think of the correct formula to translate the svg as zooming in is done. Preferably I would see a solution that doesn't use an external library as the code below is a simplified version of a bigger project that has quite complicated stuff that would propably not be as easy to refactor to another component.
let zoomedIn = false;
let svgViewboxWidth, svgViewboxHeight;
let factor = 1;
function init() {
svgViewboxWidth = 335.2262416700105;
svgViewboxHeight = 304.65863467997406;
let svgContainer = document.getElementById('svgContainer');
svgContainer.addEventListener('click', svgClick);
}
function svgClick(event) {
let newCalcX = 28, newCalcY = -86;
let gZoom = document.getElementById('zoomscale');
if (zoomedIn) {
// zoom out
factor = 1;
newCalcX = 0;
newCalcY = 0;
zoomedIn = false;
} else {
// zoom in
factor = 3;
newCalcX = (event.clientX - 86) * -1;
newCalcY = (event.clientY + 28) * -1;
zoomedIn = true;
}
let transformValue = `translate(${newCalcX}, ${newCalcY}) scale(${factor})`;
gZoom.setAttribute('transform', transformValue);
}
body {
margin: 0px;
padding: 0px;
}
.container {
width: 100%;
height: 100%;
}
<?xml version='1.0' encoding='UTF-8'?>
<html>
<head>
<title>SVG zoom issue</title>
</head>
<body onload="init()">
<div id="svgContainer" class="container">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" id="view" height="100%" width="100%" viewBox="0 0 335.2262416700105 304.65863467997406" preserveAspectRatio="xMidYMid meet" version="1.1" class="view">
<style>svg { background-color:rgba(64, 64, 64, 0.1); }</style>
<g id="zoomscale">
<line x1="20" x2="310" y1="150" y2="150" style="stroke: #000000; stroke-width: 1px" />
<line x1="165" x2="165" y1="20" y2="280" style="stroke: #000000; stroke-width: 1px" />
<circle cx="165" cy="150" r="20" style="stroke: #000000; stroke-width: 1px; fill: none" />
<circle cx="165" cy="150" r="40" style="stroke: #000000; stroke-width: 1px; fill: none" />
<circle cx="165" cy="150" r="60" style="stroke: #000000; stroke-width: 1px; fill: none" />
<circle cx="165" cy="150" r="90" style="stroke: #000000; stroke-width: 1px; fill: none" />
<circle cx="165" cy="150" r="120" style="stroke: #000000; stroke-width: 1px; fill: none" />
</g>
</svg>
</div>
</body>
<html>
I feel like I am missing something obvious, or maybe I am just not able to calculate the correct translate- values for zooming. Massive thanks to the one(s) who can help me with this!
I have 4 SVG circles and I want to toggle their style on hover and on click. I only want one circle at a time to have the style when clicked, i.e the style is removed from one circle when another one is clicked.
The hover works using CSS, but my JavaScript isn't quite correct.
It removes the style from one circle when another one is clicked but doesn't let you toggle the style class from the active target circle. Also how can I remove the style from a circle if anything outside the SVG circles is clicked?
var circles = document.querySelectorAll('.svgcircle')
circles = Array.prototype.slice.call(circles);
for (i = 0; i < circles.length; i++) {
(function(index) {
circles[index].addEventListener("click", function() {
var targetcircle = event.target;
for (var j = 0; j < circles.length; j++) {
circles[j].classList.remove("circleTarget");
}
targetcircle.classList.toggle("circleTarget");
})
})(i);
}
html,
body {
height: 100%;
width: 100%;
overflow: hidden;
}
svg {
position: absolute;
top: 35%;
left: 50%;
margin-left: -200px;
padding: 10px;
}
svg circle {
fill: #B5EF8A;
cursor: pointer;
stroke: #56CBF9;
stroke-width: 2px;
}
svg circle:hover {
fill: #fff;
stroke: #56CBF9;
}
.circleTarget {
fill: #fff;
stroke: #56CBF9;
}
<svg height="100" width="400" id="svg">
<circle class="svgcircle" cx="50" cy="50" r="40" />
<circle class="svgcircle" cx="150" cy="50" r="40" />
<circle class="svgcircle" cx="250" cy="50" r="40" />
<circle class="svgcircle" cx="350" cy="50" r="40" />
</svg>
Many Thanks.
Your code is clearing out the use of classes on all your circles and then toggling the class on the one that got clicked, but that will always lead to the clicked circle becoming active (because you just cleared out all the classes). You'll need to check to see if the circle being clicked was already active and act based on that.
By using event delegation, you can make the code much simpler. You won't have to set up event handlers on each circle and you can easily check for clicks that were not on circles. This is called event delegation.
Also, since you are converting your circles into an Array, use the Array.forEach() method to loop, which is much simpler than managing loop indexes.
See comments inline:
var circles = Array.prototype.slice.call(document.querySelectorAll('.svgcircle'));
// Setting the event listener on the document, kills two birds
// with one stone. It removes the need to set click event handlers
// on each circle and it allows for easy checking to see if you
// clicked on anything other than a circle.
document.addEventListener("click", function(evt){
// Check to see if the clicked element was one of the circles:
if(evt.target.classList.contains("svgcircle")){
// It was, so capture whether the clicked circle is active already
let active = evt.target.classList.contains("circleTarget");
removeClass(); // Reset the class usage on all the circles
// If the clicked circle was active, deactivate it.
// Otherwise, activate it:
if(active){
evt.target.classList.remove("circleTarget");
} else {
evt.target.classList.add("circleTarget");
}
} else {
// It wasn't, so clear all the styling from all the circles
removeClass();
}
});
function removeClass(){
// Loop over all the circles and remove the target class
circles.forEach(function(cir){
cir.classList.remove("circleTarget");
});
}
html,
body {
height: 100%;
width: 100%;
overflow: hidden;
}
svg {
position: absolute;
top: 35%;
left: 50%;
margin-left: -200px;
padding: 10px;
}
svg circle {
fill: #B5EF8A;
cursor: pointer;
stroke: #56CBF9;
stroke-width: 2px;
}
svg circle:hover {
fill: #fff;
stroke: #56CBF9;
}
.circleTarget {
fill: #fff;
stroke: #56CBF9;
}
<svg height="100" width="400" id="svg">
<circle class="svgcircle" cx="50" cy="50" r="40" />
<circle class="svgcircle" cx="150" cy="50" r="40" />
<circle class="svgcircle" cx="250" cy="50" r="40" />
<circle class="svgcircle" cx="350" cy="50" r="40" />
</svg>
This will perform how you expect it to:
let circles = Array.from(document.querySelectorAll('.svgcircle'));
circles.forEach(circle => {
circle.addEventListener("click", ({target}) => {
circles.forEach(c => target !== c && c.classList.remove("circleTarget"));
target.classList.toggle("circleTarget");
})
});
// Remove class if anything else is clicked
document.body.addEventListener('click', ({target}) =>
!Array.from(target.classList).includes('svgcircle')
&& circles.forEach(c => c.classList.remove("circleTarget")));
html,
body {
height: 100%;
width: 100%;
overflow: hidden;
}
svg {
position: absolute;
top: 35%;
left: 50%;
margin-left: -200px;
padding: 10px;
}
svg circle {
fill: #B5EF8A;
cursor: pointer;
stroke: #56CBF9;
stroke-width: 2px;
}
svg circle:hover {
fill: #fff;
stroke: #56CBF9;
}
.circleTarget {
fill: #fff;
stroke: #56CBF9;
}
<svg height="100" width="400" id="svg">
<circle class="svgcircle" cx="50" cy="50" r="40" />
<circle class="svgcircle" cx="150" cy="50" r="40" />
<circle class="svgcircle" cx="250" cy="50" r="40" />
<circle class="svgcircle" cx="350" cy="50" r="40" />
</svg>
Hope this helps,
So I have this bit of code for two SVG arrows that squint when clicked on (via Snap.svg) and I want to have one of them move up and down along the y axis in a loop to make it look like it's hovering while the other one is active (clicked upon).
The Snap documentation has a bit about translating matrices but has nothing on animating them. I'm assuming this is possible with Snap (some way or other), so how is it done?
var suspend = true;
Array.prototype.slice.call(document.querySelectorAll('.arrow_button')).forEach(function(el) {
var s = Snap(el.querySelector('svg')),
path = s.select('path'),
squint = el.getAttribute('data-path-squint'),
resetPath = path.attr('d'),
callback = function() {
path.animate({
'path': resetPath
}, 1300, mina.elastic);
setTimeout(function() {
suspend = true;
}, 150)
};
el.addEventListener('click', function() {
if (suspend) {
path.animate({
'path': squint
}, 100, mina.linear, callback);
suspend = false;
};
});
});
.arrow_button {
width: 55px;
height: 79.8px;
position: fixed;
top: 50%;
margin-top: -40.5px;
cursor: pointer;
}
.arrow_styles {
fill: none;
stroke: #000;
stroke-width: 10;
stroke-linecap: round;
}
.left {
left: 7px;
}
.right {
right: 7px;
}
<script src="https://cdn.jsdelivr.net/snap.svg/0.4.1/snap.svg-min.js"></script>
<div class="left arrow_button" onclick="" preserveAspectRatio="none" data-path-squint="M46.2,16.1L14,35.5C8.4,40,8.6,47,14,51.1l32.2,19.6">
<svg id="left_arrow" class="arrow_styles" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 60 86.8">
<path d="M43.7,8.1L20.2,33.2c-5.1,5.5-5.1,14.6,0,20.1l23.5,25.3" />
</svg>
</div>
<div class="right arrow_button" onclick="" preserveAspectRatio="none" data-path-squint="M9.9,16.1l32.2,19.4c5.6,4.5,5.5,11.5,0,15.5L9.9,70.6">
<svg id="right_arrow" class="arrow_styles" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 60 86.8">
<path d="M16.3,8.1l23.5,25.1c5.1,5.5,5.1,14.6,0,20.1L16.3,78.6" />
</svg>
</div>