I need to highlight the state when mouse hovers over by adding stroke around the state and adding a drop shadow. But when hover is off, the border stroke is gone, but the drop shadow stays. Is there anyway to remove the drop shadow?
Drop shadow is implemented as a filter. Is it possible to remove a filter at all?
Sample code is here: https://codepen.io/lima01/pen/OJmqgXv
var hoverState = polygonTemplate.states.create("hover");
hoverState.properties.fill = am4core.color("#367B25");
hoverState.properties.stroke = am4core.color("#ff0000");
hoverState.properties.strokeWidth = 5;
var hoverShadow = hoverState.filters.push(new am4core.DropShadowFilter);
hoverShadow.dx = 6;
hoverShadow.dy = 6;
hoverShadow.opacity = 0.3;
Thanks!
You need to set a default filter for the polygon's default state so that it knows what to revert itself to when the hover state is no longer active. You can do this by setting a DropShadowFilter for the polygon's defaultState to with a zero opacity:
var defaultFilter = polygonTemplate.defaultState.filters.push(
new am4core.DropShadowFilter()
);
defaultFilter.opacity = 0;
I'm trying to create a column chart with minimum pixel height columns. This is easy in chartjs (set the minBarLength property), however I can't figure out how to do so in amcharts.
I've tried setting series.column.template.minHeight to no effect. I've also tried setting pixelHeight and height, but also with no luck.
one workaround can be using scrollbar in Y axis like this following:
chart.scrollbarY = new am4core.Scrollbar();
chart.scrollbarY.start = 0;
chart.scrollbarY.end = 0.08;
with value Axis
valueAxis.min = 0;
valueAxis.max = 10;
This is not the best solution i agree but you can use it to slightly zoom with the buttons and scale and see
let me know if it works!
With amcharts4, I would like to add an image to a ValueAxis's label. Is this possible? I've found out, how to add an SVG image to a bullet (see demo). But is it possible to do something similar with the label of the ValueAxis?
Here is an example of such an image, I would like to add next to the number:
Currently, the value axis looks like this and it should have an icon next to the label:
EDIT:
Thanks to #zeroin's answer, I found a solution. However, there seems to be a strange bug when using an axis bullet with a template. Not every bullet is hidden even though its label is hidden. Here's the relevant code, a screenshot and a codepen to reproduce.
var valueAxis = chart.yAxes.push(new am4charts.ValueAxis());
valueAxis.min = 0;
valueAxis.max = 800;
valueAxis.renderer.minGridDistance = 30;
valueAxis.renderer.opposite = true;
valueAxis.renderer.labels.template.dx = 13;
valueAxis.strictMinMax = true;
var image = new am4core.Image();
image.href = "https://s3-us-west-2.amazonaws.com/s.cdpn.io/t-160/man-user.svg";
image.width = 12;
image.horizontalCenter = "middle";
image.verticalCenter = "middle";
image.dx = 13;
image.dy = -0.5;
image.opacity = 0.3;
valueAxis.dataItems.template.bullet = image;
EDIT2:
This bug seems to be fixed in the current release of amcharts (4.5.14). It works now as expected.
Your question is right on time - in a version released today (4.5.9) we added bullet property to AxisDataItem. So you can add image or any other sprite to all axis labels via template or create axis range at a value you need and add bullet there, like:
var range = valueAxis.axisRanges.create();
range.value = 1500;
//range.label.text = "1500";
var image = new am4core.Image();
image.href = "https://s3-us-west-2.amazonaws.com/s.cdpn.io/t-160/man-user.svg";
image.width = 15;
image.horizontalCenter = "middle";
image.verticalCenter = "middle";
image.dx = -55;
range.bullet = image;
https://codepen.io/team/amcharts/pen/JgXqvp
I would like to create a bar chart based on dates in x-axis. Labels should be displayed as month (i.e. Jan, Jan'17 - preferred). Within my data I have always first date of following months, i.e. 01Jan, 01Feb, 01Mar. I have created a chart but I am not able to make it aligned.
var chart = dc.barChart("#" + el.id);
var chCategory = ndx.dimension(function(d) {return d[chCategoryName];});
chValues = chCategory.group().reduceSum(
return parseFloat(d[chValueName]);});
//set range for x-axis
var minDate = chCategory.bottom(1)[0][chCategoryName];
var maxDate = chCategory.top(1)[0][chCategoryName];
chart
.width(800)
.height(200)
.x(d3.time.scale().domain([minDate,maxDate]))
.xUnits(d3.time.months)
.dimension(chCategory)
.group(chValues)
.renderHorizontalGridLines(true)
// .centerBar(true) //does not look better
.controlsUseVisibility(true)
.ordinalColors(arrColors)
.transitionDuration(1000)
.margins({top: 10, left: 80, right: 5, bottom: 20})
I have already read post: dc.js x-axis will not display ticks as months, shows decimals instead
but I am not able to implement it in a way that will keep correct sorting for different years.
dc.js takes the domain pretty literally - the x axis stretches exactly from the beginning to the end, disregarding the width of the bars or their placement. It's a design bug.
Here are two workarounds.
keep bars centered and add padding
If you're using elasticX you can manually correct it like this:
chart.centerBar(true)
.xAxisPadding(15).xAxisPaddingUnit('day')
If you're just setting the domain manually, that's
minDate = d3.time.day.offset(minDate, -15);
maxDate = d3.time.day.offset(maxDate, 15);
align the ticks to the left of bars and correct the right side of the domain
You don't say what problem you run into when you don't center the bars. But I know the right bar can get clipped.
If you want the elasticX effect, you can implement it manually like this, offsetting the right side by a month (example):
function calc_domain(chart) {
var min = d3.min(chart.group().all(), function(kv) { return kv.key; }),
max = d3.max(chart.group().all(), function(kv) { return kv.key; });
max = d3.time.month.offset(max, 1);
chart.x().domain([min, max]);
}
chart.on('preRender', calc_domain);
chart.on('preRedraw', calc_domain);
Or without elasticX that's just:
maxDate = d3.time.month.offset(maxDate, 1);
For making Photo Collage Maker, I use fabric js which has an object-based clipping feature. This feature is great but the image inside that clipping region cannot be scaled, moved or rotated. I want a fixed position clipping region and the image can be positioned inside the fixed clipping area as the user want.
I googled and find very near solution.
var canvas = new fabric.Canvas('c');
var ctx = canvas.getContext("2d");
ctx.beginPath();
ctx.rect(10,10,150,150);
ctx.rect(180,10,200,200);
ctx.closePath();
ctx.stroke();
ctx.clip();
Multiple Clipping Areas on fabric js canvas
where the image of one clipping region has appeared in another clipping region. How can I avoid this or is there another way of accomplishing this using fabric js.
This can be accomplished with Fabric using the clipTo property, but you have to 'reverse' the transformations (scale and rotation), in the clipTo function.
When you use the clipTo property in Fabric, the scaling and rotation are applied after the clipping, which means that the clipping is scaled and rotated with the image. You have to counter this by applying the exact reverse of the transformations in the clipTo property function.
My solution involves having a Fabric.Rect serve as the 'placeholder' for the clip region (this has advantages because you can use Fabric to move the object around and thus the clip region.
Please note that my solution uses the Lo-Dash utility library, particularly for _.bind() (see code for context).
Example Fiddle
Breakdown
1. Initialize Fabric
First, we want our canvas, of course:
var canvas = new fabric.Canvas('c');
2. Clip Region
var clipRect1 = new fabric.Rect({
originX: 'left',
originY: 'top',
left: 180,
top: 10,
width: 200,
height: 200,
fill: 'none',
stroke: 'black',
strokeWidth: 2,
selectable: false
});
We give these Rect objects a name property, clipFor, so the clipTo functions can find the one by which they want to be clipped:
clipRect1.set({
clipFor: 'pug'
});
canvas.add(clipRect1);
There doesn't have to be an actual object for the clip region, but it makes it easier to manage, as you're able to move it around using Fabric.
3. Clipping Function
We define the function which will be used by the images' clipTo properties separately to avoid code duplication:
Since the angle property of the Image object is stored in degrees, we'll use this to convert it to radians.
function degToRad(degrees) {
return degrees * (Math.PI / 180);
}
findByClipName() is a convenience function, which is using Lo-Dash, to find the with the clipFor property for the Image object to be clipped (for example, in the image below, name will be 'pug'):
function findByClipName(name) {
return _(canvas.getObjects()).where({
clipFor: name
}).first()
}
And this is the part that does the work:
var clipByName = function (ctx) {
var clipRect = findByClipName(this.clipName);
var scaleXTo1 = (1 / this.scaleX);
var scaleYTo1 = (1 / this.scaleY);
ctx.save();
ctx.translate(0,0);
ctx.rotate(degToRad(this.angle * -1));
ctx.scale(scaleXTo1, scaleYTo1);
ctx.beginPath();
ctx.rect(
clipRect.left - this.left,
clipRect.top - this.top,
clipRect.width,
clipRect.height
);
ctx.closePath();
ctx.restore();
}
NOTE: See below for an explanation of the use of this in the function above.
4. fabric.Image object using clipByName()
Finally, the image can be instantiated and made to use the clipByName function like this:
var pugImg = new Image();
pugImg.onload = function (img) {
var pug = new fabric.Image(pugImg, {
angle: 45,
width: 500,
height: 500,
left: 230,
top: 170,
scaleX: 0.3,
scaleY: 0.3,
clipName: 'pug',
clipTo: function(ctx) {
return _.bind(clipByName, pug)(ctx)
}
});
canvas.add(pug);
};
pugImg.src = 'https://fabricjs.com/lib/pug.jpg';
What does _.bind() do?
Note that the reference is wrapped in the _.bind() function.
I'm using _.bind() for the following two reasons:
We need to pass a reference Image object to clipByName()
The clipTo property is passed the canvas context, not the object.
Basically, _.bind() lets you create a version of the function that uses the object you specify as the this context.
Sources
https://lodash.com/docs#bind
https://fabricjs.com/docs/fabric.Object.html#clipTo
https://html5.litten.com/understanding-save-and-restore-for-the-canvas-context/
I have tweaked the solution by #natchiketa as the positioning of the clip region was not positioning correctly and was all wonky upon rotation. But all seems to be good now. Check out this modified fiddle:
https://jsfiddle.net/PromInc/ZxYCP/
The only real changes were made in the clibByName function of step 3 of the code provided by #natchiketa. This is the updated function:
var clipByName = function (ctx) {
this.setCoords();
var clipRect = findByClipName(this.clipName);
var scaleXTo1 = (1 / this.scaleX);
var scaleYTo1 = (1 / this.scaleY);
ctx.save();
var ctxLeft = -( this.width / 2 ) + clipRect.strokeWidth;
var ctxTop = -( this.height / 2 ) + clipRect.strokeWidth;
var ctxWidth = clipRect.width - clipRect.strokeWidth + 1;
var ctxHeight = clipRect.height - clipRect.strokeWidth + 1;
ctx.translate( ctxLeft, ctxTop );
ctx.rotate(degToRad(this.angle * -1));
ctx.scale(scaleXTo1, scaleYTo1);
ctx.beginPath();
ctx.rect(
clipRect.left - this.oCoords.tl.x,
clipRect.top - this.oCoords.tl.y,
ctxWidth,
ctxHeight
);
ctx.closePath();
ctx.restore();
}
Two minor catches I found:
Adding a stroke to the clipping object seems to throw things off by a few pixels. I tried to compensate for the positioning, but then upon rotation, it would add 2 pixels to the bottom and right sides. So, I've opted to just remove it completely.
Once in a while when you rotate the image, it will end up with a 1px spacing on random sides in the clipping.
Update to #Promlnc answer.
You need to replace the order of context transformations in order to perform proper clipping.
translation
scaling
rotation
Otherwise, you will get wrongly clipped object - when you scale without keeping aspect ratio (changing only one dimension).
Code (69-72):
ctx.translate( ctxLeft, ctxTop );
ctx.rotate(degToRad(this.angle * -1));
ctx.scale(scaleXTo1, scaleYTo1);
Replace to:
ctx.translate( ctxLeft, ctxTop );
ctx.scale(scaleXTo1, scaleYTo1);
ctx.rotate(degToRad(this.angle * -1));
See this:
https://jsfiddle.net/ZxYCP/185/
Proper result:
UPDATE 1:
I have developed a feature to clip by polygon:
https://jsfiddle.net/ZxYCP/198/
This can be done much more easily. Fabric provides render method to clip by the context of another object.
Checkout this fiddle. I saw this on a comment here.
obj.clipTo = function(ctx) {
ctx.save();
ctx.setTransform(1, 0, 0, 1, 0, 0);
clippingRect.render(ctx);
ctx.restore();
};
As I tested all fiddles above they have one bug. It is when you will flip X and Y values together, clipping boundaries will be wrong. Also, in order not doing all calculations for placing images into the right position, you need to specify originX='center' and originY='center' for them.
Here is a clipping function update to original code from #natchiketa
var clipByName = function (ctx) {
var clipRect = findByClipName(this.clipName);
var scaleXTo1 = (1 / this.scaleX);
var scaleYTo1 = (1 / this.scaleY);
ctx.save();
ctx.translate(0,0);
//logic for correct scaling
if (this.getFlipY() && !this.getFlipX()){
ctx.scale(scaleXTo1, -scaleYTo1);
} else if (this.getFlipX() && !this.getFlipY()){
ctx.scale(-scaleXTo1, scaleYTo1);
} else if (this.getFlipX() && this.getFlipY()){
ctx.scale(-scaleXTo1, -scaleYTo1);
} else {
ctx.scale(scaleXTo1, scaleYTo1);
}
//IMPORTANT!!! do rotation after scaling
ctx.rotate(degToRad(this.angle * -1));
ctx.beginPath();
ctx.rect(
clipRect.left - this.left,
clipRect.top - this.top,
clipRect.width,
clipRect.height
);
ctx.closePath();
ctx.restore();
}
Please check the updated fiddle
With the latest update on fabric 1.6.0-rc.1, you are able to skew the image by hold shift and drag the middle axis.
I have trouble with how to reverse the skew so that the clipping area stays the same. I have tried the following code to try to reverse it back, but didn't work.
var skewXReverse = - this.skewX;
var skewYReverse = - this.skewY;
ctx.translate( ctxLeft, ctxTop );
ctx.scale(scaleXTo1, scaleYTo1);
ctx.transform(1, skewXReverse, skewYReverse, 1, 0, 0);
ctx.rotate(degToRad(this.angle * -1));
Demo: https://jsfiddle.net/uimos/bntepzLL/5/
Update to previous guys answers.
ctx.rect(
clipRect.oCoords.tl.x - this.oCoords.tl.x - clipRect.strokeWidth,
clipRect.oCoords.tl.y - this.oCoords.tl.y - clipRect.strokeWidth,
clipRect.oCoords.tr.x - clipRect.oCoords.tl.x,
clipRect.oCoords.bl.y - clipRect.oCoords.tl.y
);
Now we are able to scale the clipping area without a doubt.