I am importing an svg from Corel ... the Corel stage is huge, about 3000x3000px. The svg created by Corel includes the w/h of the stage in the <svg> element:
<svg ... width="2479px" height="3504px" ... >
I import that svg into html via fabricjs:
$("#loadSVG")
.click( function()
{ fabric.loadSVGFromURL( gvs_svgSrc ,
function( A , B ){ callback( A , B ) } ,
function( M , N ){ reviver ( M , N ) }
);
});
Now I would like to scale my fabric objects according to the relationship between my canvas w/h vs the svg w/h.
QUESTION: how can I get the <svg> w/h attributes using fabricjs?
NOTE: I suppose I could do this with javascript/jquery, but I would like to think that fabric already has this information stored in it.
NOTE: I should mention that the vector shapes coming from Corel are broken up into many different groups inside fabric's reviver function. In other words they are not all in ONE group (which would allow me to query the group's w/h.
In your callback function (the second argument), B is an object with the svg canvas width and height properties.
Related
I have two SVG files: intial.svg and final.svg. I want to morph initial.svg onto final.svg on button click event. I have gone through the libraries suggested in this question but there is no clear documentation or example on how to achieve this specific morph. I have exported these animations from an XD prototype. I want to achieve a simple ease-in animation by specifying the initial state of an svg and the final state of the same svg. Any recommendations would be highly appreciated.
If the SVGs are (or can be) drawn from the same paths, then I would suggest the NPM library svg-path-morph. It allows you to interpolate freely between an arbitrary number of SVG paths.
An example of its usage:
import { compile, morph } from 'svg-path-morph'
// Get the d attributes of the <path> elements you want to morph between
const happy = document.getElemenyById('happy').getAttribute('d')
const angry = document.getElemenyById('angry').getAttribute('d')
// Compile the morph base (average path embedding)
const compiled = compile([
happy,
angry
])
// Morph between the happy/angry faces
const slightlyAngry = morph(
compiled,
[
0.80, // 80% happy
0.20 // 20% angry
]
)
// Use the face is the d attribute of a <path> element
document.getElementById('the-face').setAttribute('d', slightlyAngry)
Using Fabric.js, I have an image object that acts like a background where other group objects (consisting of rectangle and text) can be placed over it.
I would like to be able to programmatically rotate the image (90 degrees clockwise or counter-clockwise) when user clicks the corresponding button, and then rotate all other group objects relative to the image.
I was able to get this to work with the Fabric.js group feature, but the performance progressively degraded over a few rotations even with just one group object.
Looking for alternatives, I changed to use the approach described in the tranformation example here: http://fabricjs.com/using-transformations. However, the group object is not placed in the expected position after rotation.
Would anyone have any pointers on what the problem might be?
Here's the relevant code that handles just one group object for simplicity:
private rotateObjects(rotationAngle: number): void
{
let groups = this.Canvas.getObjects().filter(x => x !== this.ActiveImage);
let imageTransform = this.ActiveImage.calcTransformMatrix();
let invertedImageTransform = fabric.util.invertTransform(imageTransform);
let annotationRelationship = fabric.util.multiplyTransformMatrices(invertedImageTransform, (groups[0]).calcTransformMatrix());
this.ActiveImage.rotate(rotationAngle);
let newTransform = fabric.util.multiplyTransformMatrices(this.ActiveImage.calcTransformMatrix(), annotationRelationship);
let options = fabric.util.qrDecompose(newTransform);
groups[0].set({
flipX: false,
flipY: false,
});
groups[0].setPositionByOrigin(new fabric.Point(options.translateX, options.translateY),
"center",
"center"
);
groups[0].set(options);
groups[0].setCoords();
this.Canvas.renderAll();
}
Here are screenshots of before and after rotation. Notice that the yellow group rectangle is not placed exactly over the words "Manny Bello".
Before rotation: https://imgur.com/RcS4ekZ
After rotation: https://imgur.com/nk8gStf
Is there a way to dynamically build an SVG using sprites in amcharts 4?
Example: screenhot
There are 20 different types which are represented by colors.
Each pin can contain a multitude of types.
So an example can be that a pin has 3 types and will consist out of 3 colors.
I have an SVG path which is a circle.
With regular JS and SVG i can create a path for each type and change the stroke color, strokedasharray and strokedashoffset.
This results in the nice circle with 3 colors.
However this seems to be impossible to do with amcharts 4.
For starters, strokedashoffset is not even a supported property for a sprite. Why would you bother supporting strokedasharray and then ignore strokedashoffet?!
The second problem is finding out how to pass data to the sprite.
This is an example of a data object I pass to the mapImageSeries class.
[{
amount: 3,
client: undefined,
colorsArr: {0: "#FFB783", 1: "#FD9797", 2: "#77A538"},
dashArray: "500,1000",
dashOffset: 1500,
divided: 500,
global: true,
groupId: "minZoom-1",
hcenter: "middle",
id: "250",
latitude: 50.53398,
legendNr: 8,
longitude: 9.68581,
name: "Fulda",
offsetsArr: {0: 0, 1: 500, 2: 1000},
scale: 0.5,
title: "Fulda",
typeIds: (3) ["4", "18", "21"],
typeMarker: " type-21 type-18 type-4",
vcenter: "bottom",
zoomLevel: 5
}]
It seems impossible to pass the colors down to the sprite.
var svgPath = 'M291,530C159,530,52,423,52,291S159,52,291,52s239,107,239,239c0,131.5-106.3,238.3-237.7,239'
var mainPin1 = single.createChild(am4core.Sprite)
mainPin1.strokeWidth = 100
mainPin1.fill = am4core.color('#fff')
mainPin1.stroke = am4core.color('#ff0000')
mainPin1.propertyFields.strokeDasharray = 'dashArray'
mainPin1.propertyFields.strokeDashoffset = 'dashOffset'
mainPin1.path = svgPath
mainPin1.scale = 0.04
mainPin1.propertyFields.horizontalCenter = 'hcenter'
mainPin1.propertyFields.verticalCenter = 'vbottom'
With what you've provided, simulating your custom SVGs is beyond the scope of what can be answered, so I'll try tackling:
applying stroke-dashoffset despite lack of innate library support. (I see you've added a feature request on GitHub for it, so why the library doesn't include it, when/if it will, can be left for discussion there.)
passing data/colors to the Sprite
For both we're going to have to wait until the instances of Sprites are ready along with their data. Presuming your single variable is a reference to a MapImageSeries.mapImages.template, we can set up an "inited" event like so:
single.events.once("inited", function(event){
// ...
});
Our data and data placeholders don't really support nested arrays/objects in general, since your colors are nested within a field, we can find them via:
event.target.dataItem.dataContext.colorsArr
You can then set the fill and stroke on the Sprite or event.target.children.getIndex(0) manually from there (in my demo below, the index will be 1 because mainPin1 is not the first/only child created on the MapImage template).
As for stroke-dashoffset, you can access the actual rendered SVGElement via sprite.group.node and just use setAttribute.
I forked our map image demo from our map image data guide and added all the above to it here:
https://codepen.io/team/amcharts/pen/6a3d87ff3bdee7b85000fe775af9e583
I have a SVG map of france in my webpage and every district is a polygon, I need that when I click a district the color change and stay that way until I click it again
this is my code
function init(evt) {
if ( window.svgDocument == null ) {
svgDoc = evt.target.ownerDocument;
}
function update(district){
$(this).find("path, polygon, circle").attr("fill", "#0d0");<- what is wrong
}
this is one of my polygons
<path id="14" d="M82.387,173.009L109.168,141h2.42l3.33-5.965l20.425,8.818l5.296,0.475l-
1.876,31.861l5.236,7.5v8.018 l-15.6-1.701l
0.273-3.599C128.127,186.407,99.635,180.95,82.387,173.009z" fill="#CCCCCC"
onclick="update('14')">
as you can see Im getting the onclick event the problem is I cant get the polygon to change color.
thanks in advance.
Check what this holds. A console.log(this) will make your life so much more easier
I'll answer 1. This will point to the window object when a function is called. So doing a $(this).attr will find the attributes in the window object and not in the object that you expect.
Since you're passing the object id in the function, you can use that to find the element in your document. You can do something like:
$('#'+ubicacion).attr("fill","blue")
Fiddle here.
So i'm trying to make a map from an .svg file I produced with Illustrator because it's a map of the Netherlands with not so straightforward regions.
All the regions have their own #ID.
Now i'm trying to color each region according to their value in the dataset. I can force color on the regions by CSS ive done so on one region but thats obviously not good solution.
If I for example try to select(#id) and then change the .attr("fill","red"); it doesnt work.
How would I update region colors by id using d3.js according to the d[1] value in the dataset ?
Files: https://gist.github.com/gordonhatusupy/9466794
Live link: http://www.gordonjakob.me/regio_map/
The problem is that your Illustrator file already specifies fill colours on the individual <path> elements, and your id values are for parent <g> elements. Child elements inherit styles from parents, but only if the child doesn't have values of its own.
There are a couple things you could do to change it:
Change the Illustrator file so that the paths have no fill. Then they will inherit a fill colour set on the parent.
Select the paths directly, using d3.selectAll("g#id path") or d3.select("g#id").selectAll("path"); either version will select all <path> elements that are descendents of the <g> elment with id "id". Then you can set the fill attribute directly to over-write the value from Illustrator.
As discussed in the comments to the main question, if you want to take this a step further and actually join the data to the elements for future reference (e.g., in an event handler), the easiest way is to loop through your dataset, select each element, then use the .datum(newData) method to attach the data to each element:
dataset.forEach(function(d){ //d is of form [id,value]
d3.select("g#"+d[0]) //select the group matching the id
.datum(d) //attach this data for future reference
.selectAll("path, polygon") //grab the shapes
.datum(d) //attach the data directly to *each* shape for future reference
.attr("fill", colour(d[1]) ); //colour based on the data
});
http://jsfiddle.net/ybAj5/6/
If you want to be able to select all the top-level <g> elements in the future, I would suggest also giving them a class, so you can select them with, for example, d3.select("g.region"). For example:
dataset.forEach(function(d){ //d is of form [id,value]
d3.select("g#"+d[0]) //select the group matching the id
.datum(d) //attach this data for future reference
.classed("region", true) //add a class, without erasing any existing classes
.selectAll("path, polygon") //grab the shapes
.datum(d) //attach the data directly to *each* shape for future reference
.attr("fill", colour(d[1]) ); //colour based on the data
});
d3.selectAll("g.region")
.on("click", function(d,i) {
infoBox.html("<strong>" + d[0] + ": </strong>" + d[1] );
//print the associated data to the page
});
Example implementation: http://jsfiddle.net/ybAj5/7/
Although using dataset.forEach doesn't seem to be using the full capability of d3, it is actually much simpler than trying to attach the whole dataset at once -- especially since there is such variability in the structure of the regions, some of which have nested <g> elements:
//Option two: select all elements at once and create a datajoin
d3.selectAll("g[id]") //select only g elements that have id values
.datum(function(){
var id=d3.select(this).attr("id");
return [id, null]; })
//create an initial [id, value] dataset based on the id attribute,
//with null value for now
.data(dataset, function(d){return d[0];})
//use the first entry in [id,value] as the key
//to match the dataset with the placeholder data we just created for each
.selectAll("path, polygon") //grab the shapes
.datum(function(){
return d3.select(this.parentNode).datum() ||
d3.select(this.parentNode.parentNode).datum();
}) //use the parent's data if it exists, else the grandparent's data
.attr("fill", function(d){return d?colour(d[1]):"lightgray";});
//set the colour based on the data, if there is a valid data element
//else use gray.
This fiddle shows the above code in action, but again I would recommend using the forEach approach.