Fabric js: overriding toSVG method to add custom attribute into svg output - svg

I am trying to add attribute into my SVG output using fabric.js library.
Here is my code:
objectGlobal = new fabric.Rect({
// here are some Rect attributes such as height width etc
room_id: counter
});
objectGlobal.toSVG = (function(toSVG) {
return function() {
console.log("overriden to svg");
return fabric.util.object.extend(toSVG.call(this), {
room_id: this.room_id
});
};
})(objectGlobal.toSVG);
canvas.add(objectGlobal);
I got "overriden to svg" in my console, but my SVG output does not contain room_id attribute.
Here is the SVG output:
<rect x="-93.5" y="-49.5" rx="0" ry="0" width="187" height="99" style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; fill: rgb(229,229,229); fill-rule: nonzero; opacity: 1;" transform="translate(318 160)"/>

Related

How to align points on the outer edges of polygon for thicker border in svg?

My question is:
I have a polygon which is drawn by taking some points and I am showing the points on the edges/outline of polygon. When I increase the stroke-width for the polygon the points getting aligned in the center of the outline. How do we achieve the points alignment on the outside of the edge/border?
Actual how it come is like below picture, the points are center aligned across the border/stroke
working area link for this is
https://codepen.io/jinata92/pen/JjKZeqE
I am looking for a solution like as below picture, The points should be aligned on the outer edges of border/stroke
var height = 100,
width = 100;
var polygon;
var arrVertexes = [
[6, 6],
[94, 6],
[94, 94],
[6, 94]
];
var svg, gContainer;
function config() {
svg = d3
.select(".main")
.attr("width", width)
.attr("height", height)
.attr("viewBox", `0 0 ${width} ${height}`);
gContainer = svg.select("g");
}
function drawPolygon() {
polygon = gContainer
.append("polygon")
.attr("points", arrVertexes)
.attr("class", "segment");
}
function drawCircle() {
gContainer
.selectAll("circle")
.data(arrVertexes)
.enter()
.append("circle")
.attr("class", "vertex")
.classed("handle", true)
.attr("cx", function(d) {
return d[0];
})
.attr("cy", function(d) {
return d[1];
})
.attr("r", 4);
}
config();
drawPolygon();
drawCircle();
body {
background-color: grey;
}
svg {
position: absolute;
overflow: visible;
}
.resize-div {
position: relative;
overflow: visible;
}
.polygon {
stroke: yellow;
fill: transparent;
}
.vertex,
.dot {
fill: black;
stroke: none;
}
.segment {
stroke-width: 30;
stroke: yellow;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<div style="width:100px;height:100px; left: 400px;
top:50px;" class="resize-div">
<svg class="main" height="100%" width="100%">
<g class="polygon"></g>
</svg>
</div>
Stroke-width is always applied to both sides, so the centre of the line is still at the required place. But there are some workarounds. One is to just draw the shape, but also apply a clip path equal to the element, but without the stroke-width:
var height = 100,
width = 100;
var polygon;
var arrVertexes = [
[6, 6],
[94, 6],
[94, 94],
[6, 94]
];
var svg, gContainer;
function config() {
svg = d3
.select(".main")
.attr("width", width)
.attr("height", height)
.attr("viewBox", `0 0 ${width} ${height}`);
gContainer = svg.select("g");
}
function drawPolygon() {
polygon = gContainer
.append("polygon")
.attr("clip-path", "url('#my-clip-path')")
.attr("points", arrVertexes)
.attr("class", "segment");
// Append a clip path
svg.append("defs")
.append("clipPath")
.attr("id", "my-clip-path")
.append("polygon")
.attr("points", arrVertexes)
.attr("class", "segment");
}
function drawCircle() {
gContainer
.selectAll("circle")
.data(arrVertexes)
.enter()
.append("circle")
.attr("class", "vertex")
.classed("handle", true)
.attr("cx", function(d) {
return d[0];
})
.attr("cy", function(d) {
return d[1];
})
.attr("r", 4);
}
config();
drawPolygon();
drawCircle();
body {
background-color: grey;
}
svg {
position: absolute;
overflow: visible;
}
.resize-div {
position: relative;
overflow: visible;
}
.polygon {
stroke: yellow;
fill: transparent;
}
.vertex,
.dot {
fill: black;
stroke: none;
}
.segment {
stroke-width: 30;
stroke: yellow;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<div style="width:100px;height:100px; left: 400px;
top:50px;" class="resize-div">
<svg class="main" height="100%" width="100%">
<g class="polygon"></g>
</svg>
</div>
Alternatively, you could draw a polygon on top of the other, but inwards a little bit. Use the bounding box to calculate the centre of the outer polygon, and move the coordinates of the inner polygon inwards by some number of pixels:
var height = 100,
width = 100;
var polygon;
var arrVertexes = [
[6, 6],
[94, 6],
[94, 94],
[6, 94]
];
var svg, gContainer;
function config() {
svg = d3
.select(".main")
.attr("width", width)
.attr("height", height)
.attr("viewBox", `0 0 ${width} ${height}`);
gContainer = svg.select("g");
}
function drawPolygon() {
polygon = gContainer
.append("polygon")
.attr("points", arrVertexes)
.attr("class", "segment-border");
// Get the bounding box so you can calculate the centre
const boundingBox = polygon.node().getBBox();
const centre = {
x: boundingBox.x + boundingBox.width / 2,
y: boundingBox.y + boundingBox.height / 2,
};
const innerVertexes = arrVertexes.map(d => [
d[0] < centre.x ? d[0] + 15 : d[0] - 15,
d[1] < centre.y ? d[1] + 15 : d[1] - 15,
]);
polygon = gContainer
.append("polygon")
.attr("points", innerVertexes)
.attr("class", "segment");
}
function drawCircle() {
gContainer
.selectAll("circle")
.data(arrVertexes)
.enter()
.append("circle")
.attr("class", "vertex")
.classed("handle", true)
.attr("cx", function(d) {
return d[0];
})
.attr("cy", function(d) {
return d[1];
})
.attr("r", 4);
}
config();
drawPolygon();
drawCircle();
body {
background-color: grey;
}
svg {
position: absolute;
overflow: visible;
}
.resize-div {
position: relative;
overflow: visible;
}
.polygon {
stroke: yellow;
fill: transparent;
}
.vertex,
.dot {
fill: black;
stroke: none;
}
.segment-border {
fill: yellow;
}
.segment {
fill: white;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<div style="width:100px;height:100px; left: 400px;
top:50px;" class="resize-div">
<svg class="main" height="100%" width="100%">
<g class="polygon"></g>
</svg>
</div>

Cross-Browser SVG rendering problems with circle and stroke-dasharray

my problem is explained quite simply. I've gotten a screenshot of the situation and snippet a jsFiddle code.
The problem I have, is clearly visible on the screenshot, the circular sections look perfectly in the Chrome browser, but in FireFox & Edge etc. the sections are slightly offset.
Prior to the current status, I had set the r / cx / cy properties to css, but that was not compatible either. I found out that you have to write them directly into the circle tag.
Has anyone ever had the problem, I mean, but can anyone explain why it does not work as expected?
[EDIT] THANKS #Sphinxxx for answer the basic question of y doesn't work that.
Is there a hack / workaround to solve the problem?
Screenshot:
Browser on this Screen:
1. Chrome
2. FireFox
3. Edge
[UPDATE] (In the current version of FireFox that issue is fixed)
Now we have that problem only in the Edge browser
Here to the code example:
const duration = 1200
Array.from(document.getElementsByClassName('count')).forEach(el => {
const target = parseInt(el.innerText)
const step = (duration / target)
const increment = step < 10 ? Math.round(10 / step) : 1
let current = 0
console.log(el.innerText + ': ' + step)
el.innerText = current
window.addEventListener('load', _ => {
const timer = setInterval(_ => {
current += increment
if (current >= target) {
el.innerText = target
clearInterval(timer)
} else
el.innerText = current
}, step)
})
})
function getlength(number) {
return number.toString().length;
}
svg.chart {
width: 100px;
border-radius: 50%;
background: yellowgreen;
transform: rotate(-90deg);
animation: grow-up cubic-bezier(0, 0, 0.18, 1) 2s;
animation-delay: 0.3s;
}
.chart > circle {
fill: none;
stroke-width: 32;
}
.chart > circle.first {
stroke: deeppink;
}
.chart > circle.second {
stroke: mediumpurple;
}
.chart > circle.third {
stroke: #fb3;
}
.chart > circle.fourth {
stroke: #ce3b6a;
}
.legend-list li{
width: 40%;
}
.legend-list span.glyphicon {
color: yellowgreen;
}
.legend-list .first span.glyphicon {
color: deeppink;
}
.legend-list .second span.glyphicon {
color: mediumpurple;
}
.legend-list .third span.glyphicon {
color: #fb3;
}
.legend-list .fourth span.glyphicon {
color: #ce3b6a;
}
svg circle {
animation: rotate-in cubic-bezier(0, 0, 0.18, 1) .7s;
animation-delay: 0.3s;
transform-origin: 50% 50%
}
#keyframes rotate-in {
from {
opacity: 0;
stroke-dashoffset: 30;
}
to {
opacity: 1;
stroke-dashoffset: 0;
}
}
#keyframes grow-up {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
<svg class="chart" viewBox="0 0 32 32">
<!-- circle zero from 0 to 100 for filling yellowgreen --> <!-- 75 - 100 = 25 % -> realy 0 - 100 background color -->
<circle class='fourth' stroke-dasharray="75 100" r="16" cx="16" cy="16"></circle> <!-- 60 - 75 = 15 % -->
<circle class='third' stroke-dasharray="60 100" r="16" cx="16" cy="16"></circle> <!-- 40 - 60 = 20 % -->
<circle class='second' stroke-dasharray="40 100" r="16" cx="16" cy="16"></circle> <!-- 30 - 40 = 10 % -->
<circle class='first' stroke-dasharray="30 100" r="16" cx="16" cy="16"></circle> <!-- 0 - 30 = 30 % -->
</svg>
Both Edge and Firefox clearly do something wrong when drawing circles where the stroke meets itself in the circle center. Your example can be simplified to this:
<svg class="chart" width="320" height="340" viewBox="1 0 32 34">
<circle cx="16" cy="1" r="8" stroke-width="15.5" stroke="green" stroke-dasharray="20 999" fill="none"></circle>
<circle cx="16" cy="18" r="8" stroke-width="16" stroke="blue" stroke-dasharray="20 999" fill="none"></circle>
</svg>
The green circle has a stroke that's just a little bit too thin to reach the center, and looks like you would expect, with a tiny hole in the middle. The blue circle should perfectly close that gap, but somehow overshoots in a strange way:
The problem might be related to this: Paths: Stroking and Offsetting, but doesn't quite look the same.

SVG text background color with border radius and padding that matches the text width

I need to wrap a background around a text element inside an SVG, it needs to have padding and a border radius. The issue is the text will be dynamic so I need the background to expand the width of the text. I found a solution to this using foreign object but this isn't support in IE 11 which is a problem. Can anyone suggest a workaround.
if you can use script, you can use this little function. It handles some of the CSS values. You could however implement whatever you need...
function makeBG(elem) {
var svgns = "http://www.w3.org/2000/svg"
var bounds = elem.getBBox()
var bg = document.createElementNS(svgns, "rect")
var style = getComputedStyle(elem)
var padding_top = parseInt(style["padding-top"])
var padding_left = parseInt(style["padding-left"])
var padding_right = parseInt(style["padding-right"])
var padding_bottom = parseInt(style["padding-bottom"])
bg.setAttribute("x", bounds.x - parseInt(style["padding-left"]))
bg.setAttribute("y", bounds.y - parseInt(style["padding-top"]))
bg.setAttribute("width", bounds.width + padding_left + padding_right)
bg.setAttribute("height", bounds.height + padding_top + padding_bottom)
bg.setAttribute("fill", style["background-color"])
bg.setAttribute("rx", style["border-radius"])
bg.setAttribute("stroke-width", style["border-top-width"])
bg.setAttribute("stroke", style["border-top-color"])
if (elem.hasAttribute("transform")) {
bg.setAttribute("transform", elem.getAttribute("transform"))
}
elem.parentNode.insertBefore(bg, elem)
}
var texts = document.querySelectorAll("text")
for (var i = 0; i < texts.length; i++) {
makeBG(texts[i])
}
text {
background: red;
border-radius: 5px;
border: 2px solid blue;
padding: 5px
}
text:nth-of-type(2) {
background: orange;
border-color: red
}
g text {
border-width: 4px
}
<svg width="400px" height="300px">
<text x="20" y="40">test text</text>
<text x="20" y="80" transform="rotate(10,20,55)">test with transform</text>
<g transform="translate(0,100) rotate(-10,20,60) ">
<text x="20" y="60">test with nested transform</text>
</g>
</svg>

How to use keyboards event while editing text in fabricjs

Is there a way to listen to key events while you are editing a text? (in itext or textbox or whatever). Our goal is to be able to hit cmd-b or cmd-i to set the selected part of your text in bold or italic.
Thanks for your help!
DEMO
window.addEventListener("keydown",onKeyDown);
function onKeyDown(e){
if (event.which == 73 && event.ctrlKey ) {
//ctrl+i
makeItalic();
}
if (event.which == 66 && event.ctrlKey ) {
//ctrl+b
makeBold();
}
}
var canvas = new fabric.Canvas('canvas');
var text = 'FabricJS Is Awsome';
var itext = new fabric.IText(text, {
left: 100,
top: 100,
fontSize: 40,
fill: '#000'
})
canvas.add(itext);
canvas.setActiveObject(itext);
function makeItalic(){
itext.setSelectionStyles({fontStyle:'italic'});// set your property
canvas.renderAll();
}
function makeBold(){
itext.setSelectionStyles({fontWeight:'bold'});
canvas.renderAll();
}
.canvas-wrappter {
position: relative;
}
canvas {
border: 1px solid #000;
}
.itext {
width: 300px;
background: transparent;
position: absolute;
z-index: 2;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.7.16/fabric.min.js"></script>
<div class="canvas-wrapper">
<canvas id="canvas" width="500" height="500"></canvas>
</div>
setSelectionStyles(style) use this function to set your style.

Make "widget" for SVG-group in D3.js

I am using D3 to manipulate an SVG, that contains several "widgets" that have behavior and can be controlled eg via events. For instance I have a spinning fan. It should be possible to turn on and off the fan. I have been able to build that in D3, but not in an elegant way, and with all the code being global. What I want to end up with is something as below:
Creation of "widget", as known from jQuery:
d3.select('svg').append('g').attr('id', 'myFan').fan();
And then I would like to turn on and off with smth like the following, also as known from jQuery:
d3.select('#myFan').fan('start')
d3.select('#myFan').fan('stop')
Is it possible to achieve this in D3, ie to create such a "widget"?
Is there generally a different approach to such a problem, when using D3?
My current unelegant solution with global code:
Robert asked for this. Code at Codepen: link.
Javascript
var fan = d3.select('body')
.append('svg')
.attr('width', 200)
.attr('height', 200)
.append('g')
.attr('class', 'fan')
.attr('id', 'myFan')
.attr('transform', 'translate(75,75)')
// Static, background disc
fan.append('circle')
.attr('cx',0)
.attr('cy',0)
.attr('r',60)
// Dynamic, rotating path
var blade = fan.append('path')
.attr('d', 'M 0 0 L -30 15 L -60 0 Z L 30 -15 L 60 0 Z ')
.attr('transform', 'translate(50,50)')
.attr('transform', 'rotate(90)');
// Static, hub
fan.append('circle')
.attr('cx', 0)
.attr('cy', 0)
.attr('r', 10)
var rotation = 0; // degrees
var speed = 10; // degree per call
function rotate() {
rotation = (rotation + speed) % 360;
blade.attr('transform', 'rotate('+rotation+')')
}
setInterval(rotate, 100);
function stop() { speed = 0; }
function start() { speed = 10; }
var ctrlPanel = d3.select('body').append('div');
ctrlPanel.append('button').text('Start').on('click', start);
ctrlPanel.append('button').text('Stop').on('click', stop);
CSS
path {
fill: red;
strok: blue;
stroke-width: 3px;
}
.fan circle {
fill: lightgray;
stroke: black;
stroke-width: 3px;
}
.fan path {
fill: darkgray;
stroke: black;
stroke-width: 3px;
}

Resources