How to make the Chart.js animate when scrolled to that section? - javascript

I am trying to use the pie chart from Chart.js (http://www.chartjs.org/docs/#pieChart-exampleUsage). Everything works smooth, but the animation happens as soon as the page loads, but since the user has to scroll down to see the chart, they won't see the animation. Is there anyway I can make the animation to start only when scrolled to that position? Also if possible, is it possible to animate everytime when that chart becomes into view?
My code is as follows:
<canvas id="canvas" height="450" width="450"></canvas>
<script>
var pieData = [
{
value: 30,
color:"#F38630"
},
{
value : 50,
color : "#E0E4CC"
},
{
value : 100,
color : "#69D2E7"
}
];
var myPie = new Chart(document.getElementById("canvas").getContext("2d")).Pie(pieData);
</script>

You can combine the check for whether something is viewable with a flag to keep track of whether the graph has been drawn since it appeared in the viewport (though doing this with the plugin bitiou posted would be simpler):
http://jsfiddle.net/TSmDV/
var inView = false;
function isScrolledIntoView(elem)
{
var docViewTop = $(window).scrollTop();
var docViewBottom = docViewTop + $(window).height();
var elemTop = $(elem).offset().top;
var elemBottom = elemTop + $(elem).height();
return ((elemTop <= docViewBottom) && (elemBottom >= docViewTop));
}
$(window).scroll(function() {
if (isScrolledIntoView('#canvas')) {
if (inView) { return; }
inView = true;
new Chart(document.getElementById("canvas").getContext("2d")).Pie(data);
} else {
inView = false;
}
});

Best to use deferred plugin
https://chartjs-plugin-deferred.netlify.com/
<script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-deferred#1"></script>
new Chart(ctx, {
// ... data ...
options: {
// ... other options ...
plugins: {
deferred: {
xOffset: 150, // defer until 150px of the canvas width are inside the viewport
yOffset: '50%', // defer until 50% of the canvas height are inside the viewport
delay: 500 // delay of 500 ms after the canvas is considered inside the viewport
}
}
}
});

I don't know if you could do that, I had the same issue and resolved it without any plugin in this simple way, check out:
$(window).bind("scroll", function(){
$('.chartClass').each(function(i){
var dougData = [
{value: 100, color:"#6abd79"},
{value: 20, color:"#e6e6e6"}
];
var graphic = new Chart(document.getElementById("html-charts").getContext("2d")).Doughnut(dougData, options);
$(window).unbind(i);
});
});

I had the same problem with Chart.js and found a really great solution.
There is a package on GitHub that is called ChartNew.js by FVANCOP.
He expanded it and added several functions.
Look at the sample, the charts are drawn by scrolling down.
Responsible is the statement
dynamicDisplay : true

Using IntersectionObserver is the more modern approach, and gives you the ability to choose how much of the element must be visible before triggering an event.
A threshold of 0 means it will trigger if any part of the element is visible, while a threshold of 1 means the entire element must be visible.
It performs better than listening to scroll, and will only fire once when the element transitions from hidden to visible, even while you are continuously scrolling. And it also works if the page content changes due to other events, such as other content being hidden/shown, or window resize, etc.
This is how I made a radial chart that animates every time at least 20% of it appears into view:
const options = {
series: [75],
chart: {
type: 'radialBar',
},
};
const chart = new ApexCharts(document.querySelector("#chart"), options);
chart.render();
const observer = new IntersectionObserver(function(entries) {
if (entries[0].isIntersecting === true) {
chart.updateSeries([0], false); // reset data to 0, then
chart.updateSeries([75], true); // set original data and animate
// you can disconnect the observer if you only want this to animate once
// observer.disconnect();
}
}, { threshold: [0.2] });
observer.observe(document.querySelector("#chart"));

This is what you want:
Check if element is visible after scrolling
Next time please check if there's already an answer ;)
Alternatively: jquery.appear

Related

Auto-scroll embedded window only once when entering viewport. Can't scroll back up

I have an image embedded in a container with a background image to give the effect of scrolling within the page. Initially, I had the scrolling effect take place on page load, with this simple bit of script which worked perfectly.
$(window).on("load", function () {
$(".embedded_scroller_image").animate({ scrollTop: $('.embedded_scroller_image')[0].scrollHeight}, 2500, "easeInOutCubic");
}); // end on load
However, the element is too far down the page now and I want that animation to fire when the element enters 80% of the viewport. That part is also working fine with this code here (I'm using a scroll limiter to improve browser performance)
// limit scroll call for performance
var scrollHandling = {
allow: true,
reallow: function() {
scrollHandling.allow = true;
},
delay: 500 //(milliseconds) adjust to the highest acceptable value
};
$(window).on('scroll', function() {
var flag = true;
if(scrollHandling.allow) { // call scroll limit
var inViewport = $(window).height()*0.8; // get 80% of viewport
$('.embedded_scroller_image').each(function() { // check each embedded scroller
var distance = $(this).offset().top - inViewport; // check when it reaches offset
if ($(window).scrollTop() >= distance && flag === true ) {
$(this).animate({ scrollTop: $(this)[0].scrollHeight}, 2500, "easeInOutCubic"); //animate embedded scroller
flag = false;
}
});
} // end scroll limit
}); // end window scroll function
The problem is this: I want the autoscroll to happen once and then stop. Right now, it works on entering viewport, but if I then try to manually scroll the image, it keeps pushing back down or stutters. You can't get the element to scroll normally. I attempted to use the flag in the code to stop the animation, but couldn't get that to successfully work.
How can I have this animation fire when the element is 80% in the viewport, but then completely stop after one time?
Here is a codepen I mocked up as well http://codepen.io/jphogan/pen/PPQwZL?editors=001 If you scroll down, you will see the image element autoscroll when it enters the viewport, but if you try to then scroll that image up in its container, it won't work.
Thanks!
I have tweaked your script a bit:
// limit scroll call for performance
var scrollHandling = {
allow: true,
reallow: function() { scrollHandling.allow = true; },
delay: 500 //(milliseconds) adjust to the highest acceptable value
};
$(window).on('scroll', function() {
if(scrollHandling.allow) { // call scroll limit
var inViewport = $(window).height()*0.8; // get 80% of viewport
$('.embedded_scroller_image').each(function() { // check each embedded scroller
var distance = $(this).offset().top - inViewport; // check when it reaches offset
if ($(window).scrollTop() >= distance ) {
$(this).animate({ scrollTop: $(this)[0].scrollHeight}, 2500, "easeInOutCubic"); //animate embedded scroller
scrollHandling.allow = false;
}
});
} // end scroll limit
}); // end window scroll function
I have kicked out your flag and simply made use of scrollHandling.allow declared already.
Try if it works for you :)
Cheers!

Javascript fade in/out effect freezes when hovering quickly over images

I'm playing around with pure JavaScript, so I created a small fade in/out object, to adjust images opacity onmouseover and onmouseout. Fading works fine when the mouseover and mouseout actions are precise:
Start moving the cursor from the white background
Hover over an image
Hover back over the white background
The problem is, as soon as I start to move the mouse "naturally" from one image to another, the fading (or rather the script itself) freezes.
I'm not sure whether it's a animation-speed problem, or there's something I'm missing in the implementation.
If someone has the time to take a look, I would appreciate a peer check, so I can crack the issue and learn new stuff.
Here's a fiddle: http://jsfiddle.net/6bd3xepe/
Thanks!
As I see it, you have one INTERVAL for you FADER, you need one for each IMG.
My jsfiddle fixes this. I added an ALT-attribute to each IMG with "dome" content, so as to circumvent the jsfiddle working on non-cat-images .. ignore that part - commented out below.
There are some fundamental things wrong with the design - keeping track of objects & references is key. Usage of "this" & "that" aren't helping in the current implementation (see comments to OP). Also, on another note, the usage of "toFixed(2)" is not really required IMHO and you can shorten "o = o + 0.1" to "o += 0.1".
JS:
var fader = {
target: document.getElementsByTagName('img'),
interval: [],
speed: 25,
default_opacity: 1,
init: function() {
this.bindEvents();
},
// Get element's opacity and increase it up to 1
fadeIn: function(element) {
var element_opacity = this.getOpacity(element),
that = this,
idx = element.getAttribute('data-idx');
console.log("fI: "+idx+" "+element_opacity);
this.default_opacity = element_opacity.toFixed(2);
this.interval[idx] = setInterval(function() {
if (element_opacity.toFixed(2) < 1) {
element_opacity = element_opacity + 0.1;
element.style.opacity = element_opacity.toFixed(2);
} else {
clearInterval(that.interval[idx]);
}
}, that.speed);
},
// Get current opacity and decrease it back to the default one
fadeOut: function(element) {
var element_opacity = this.getOpacity(element),
that = this,
idx = element.getAttribute('data-idx');
console.log("fO: "+idx+" "+element_opacity);
this.interval[idx] = setInterval(function() {
if (element_opacity.toFixed(2) > that.default_opacity) {
element_opacity = element_opacity - 0.1;
element.style.opacity = element_opacity.toFixed(2);
} else {
clearInterval(that.interval[idx]);
element.removeAttribute('style');
}
}, that.speed);
},
// Get opacity of an element using computed styles
getOpacity: function(element) {
var styles = window.getComputedStyle(element),
opacity = parseFloat(styles.getPropertyValue('opacity'));
return opacity;
},
bindEvents: function() {
var that = this, count = 0;
for (var i in this.target) {
// the whole "dome" is just a fsfiddle hack - otherwise it sees 7 images instead of 4!
//if( this.target[i].alt == "dome" ){
console.log("COUNT: "+count);
this.target[i].setAttribute('data-idx',count);
this.target[i].onmouseover = function() {
that.fadeIn(this);
}
this.target[i].onmouseout = function() {
that.fadeOut(this);
}
count++;
//}
}
}
};
fader.init();

How to change pop-up location of google charts tooltip

I currently have html enabled tooltips that also display "sub graphs". However, it would be nice if it was possible to have all tooltips pop up in a fixed location or have an offset that adjusted their relative poition.
This is an example of the kind of tooltip that I have (blank data). I'd like to move it to the right. Any suggestions would be appreciated, including any javascript trickery.
whilst the answer is very good it is a little outdated now. Google has implemented CSS control so there is greater flexibility without the need to hack the JavaScript.
.google-visualization-tooltip { position:relative !important; top:0 !important;right:0 !important; z-index:+1;}
will provide a tooltip fixed at the bottom of the chart, live example: http://www.taxformcalculator.com/federal-budget/130000.html
alternatively you could just tweak the left margin...
.google-visualization-tooltip { margin-left: 150px !important; z-index:+1;}
Note that pulling the container forward with z-index reduces (but does not stop entirely) visibility flicker as the mouse moves. The degree of flicker will vary on chart size, call etc. Personally, I prefer to fix the tool tip and make it part of the design as per the first example. Hope this helps those who are deterred by the JS hack (which is good but really no longer necessary).
The tooltip position is set inline, so you need to listen for DOM insertion of the tooltip and change the position manually. Mutation events are deprecated, so use a MutationObserver if it is available (Chrome, Firefox, IE11) and a DOMNodeInserted event handler if not (IE 9, 10). This will not work in IE8.
google.visualization.events.addOneTimeListener(myChart, 'ready', function () {
var container = document.querySelector('#myChartDiv > div:last-child');
function setPosition () {
var tooltip = container.querySelector('div.google-visualization-tooltip');
tooltip.style.top = 0;
tooltip.style.left = 0;
}
if (typeof MutationObserver === 'function') {
var observer = new MutationObserver(function (m) {
for (var i = 0; i < m.length; i++) {
if (m[i].addedNodes.length) {
setPosition();
break; // once we find the added node, we shouldn't need to look any further
}
}
});
observer.observe(container, {
childList: true
});
}
else if (document.addEventListener) {
container.addEventListener('DOMNodeInserted', setPosition);
}
else {
container.attachEvent('onDOMNodeInserted', setPosition);
}
});
The MutationObserver should be fine, but the events may need some work; I didn't test them.
I had more or less the same question as Redshift, having been trying to move the tooltip relative to the node being hovered over. Using asgallant's fantastic answer I've implemented his code as below.
I haven't been able to test whether this works with the MutationObserver because during my testing in Firefox, Chrome and IE11 it always fails that test and uses addEventListener. The docs suggest it should work though.
I had to introduce a timeout to actually manipulate the styles as otherwise the left and top position of the element was always reported as 0. My assumption is that the event fired upon addition of the node but the DOM wasn't quite ready. This is just a guess though and I'm not 100% happy with implementing it in this way.
var chart = new google.visualization.LineChart(document.getElementById('line_chart'));
google.visualization.events.addOneTimeListener(chart, 'ready', function () {
var container = document.querySelector('#line_chart > div:last-child');
function setPosition(e) {
if (e && e.target) {
var tooltip = $(e.target);
setTimeout(function () {
var left = parseFloat(tooltip.css('left')) - 49;
var top = parseFloat(tooltip.css('top')) - 40;
tooltip.css('left', left + 'px');
tooltip.css('top', top + 'px');
$(".google-visualization-tooltip").fadeIn(200);
}, 1);
}
else {
var tooltip = container.querySelector('.google-visualization-tooltip');
var left = parseFloat(tooltip.style.left) - 49;
var top = parseFloat(tooltip.style.top) - 40;
tooltip.style.left = left + 'px';
tooltip.style.top = top + 'px';
$(".google-visualization-tooltip").fadeIn(200);
}
}
if (typeof MutationObserver === 'function') {
var observer = new MutationObserver(function (m) {
if (m.length && m[0].addedNodes.length) {
setPosition(m);
}
});
observer.observe(container, {
childList: true
});
}
else if (document.addEventListener) {
container.addEventListener('DOMNodeInserted', setPosition);
}
else {
container.attachEvent('onDOMNodeInserted', setPosition);
}
});
chart.draw(data, options);
}
EDIT: Updated to get the MutationObserver working following asgallant's comment.

How to prevent loss hover in Raphael?

I'm developing some page when I use Raphael liblary to draw some items.
my App
So my problem is in that when I'm moving to some rect it growing up but when my mouse is on text which is positioning on my rect, it loss his hover. You can see it on my app example.
var paper = new Raphael(document.getElementById('holder'), 500, object.length * 100);
drawLine(paper, aType.length, bType.length, cType.length, cellSize, padding);
process = function(i,label)
{
txt = paper.text(390,((i+1)* cellSize) - 10,label.devRepo)
.attr({ stroke: "none", opacity: 0, "font-size": 20});
var a = paper.rect(200, ((i+1)* cellSize) - 25, rectWidth, rectHeight)
.hover(function()
{
this.animate({ transform : "s2"}, 1000, "elastic");
this.prev.animate({opacity: 1}, 500, "elastic");
this.next.attr({"font-size" : 30});
},
function()
{
this.animate({ transform : "s1" }, 1000, "elastic");
this.prev.animate({opacity: 0}, 500);
this.next.attr({"font-size" : 15});
});
}
I have tried e.preventDefault(); on hover of this.next and some other solutions but it's doesn't work.
Any help would be appreciated.
Most people will suggest you place a transparent rectangle over the box and the labels and attach the hover functions to that instead. (If memory serves, you have to make the opacity 0.01 instead of 0 to prevent the object from losing its attached events.) This works fine, but I don't love this solution; it feels hacky and clutters the page with unnecessary objects.
Instead, I recommend this: Remove the second function from the hover, making it functionally a mouseover function only. Before you draw any of the rectangles and labels, make a rectangular "mat" the size of the paper. Then, attach the function that minimizes the label as a mouseover on the mat. In other words, you're changing the trigger from mousing out of the box to mousing over the area outside of it.
I left a tiny bit of opacity and color on the mat to be sure it's working. You can just change the color to your background color.
var mat = paper.rect(0, 0, paper.width, paper.height).attr({fill: "#F00", opacity: 0.1});
Now, you want to make a container for all the rectangles so you can loop through them to see which need to be minimized. I made an object called "rectangles" that contains the objects we're concerned with. Then:
mat.mouseover(function () {
for (var c = 0; c < rectangles.length; c += 1) {
//some measure to tell if rectangle is presently expanded
if (rectangles[c].next.attr("font-size")) {
rectangles[c].animate({
transform : "s1"
}, 1000, "elastic");
rectangles[c].prev.animate({opacity: 0}, 500);
rectangles[c].next.attr({"font-size" : 15});
}
}
});
Then I just removed the mouseout function from the individual rectangles.
jsBin
To be clear, this will have some downsides: If people run the mouse around really fast, they can expand several rectangles at the same time. This is remedied as soon as the mouse touches the mat. I think the functionality looks pretty nice. But the invisible mats is always an option.
I wrote a small extension to Raphael - called hoverInBounds - that resolves this limitation.
Demo: http://jsfiddle.net/amustill/Bh276/1
Raphael.el.hoverInBounds = function(inFunc, outFunc) {
var inBounds = false;
// Mouseover function. Only execute if `inBounds` is false.
this.mouseover(function() {
if (!inBounds) {
inBounds = true;
inFunc.call(this);
}
});
// Mouseout function
this.mouseout(function(e) {
var x = e.offsetX || e.clientX,
y = e.offsetY || e.clientY;
// Return `false` if we're still inside the element's bounds
if (this.isPointInside(x, y)) return false;
inBounds = false;
outFunc.call(this);
});
return this;
}

Possible to make jqGrid stretch to 100%?

Is it possible to make it so that a jqGrid will have a width set to 100%? I understand that column widths must be an absolute pixel size, but I've yet to find anything for setting the width of the actual grid to a relative size. For instance, I want to set the width to 100%. Instead of 100% it seems to use an odd size of 450px. There is more horizontal room on the page, but with the columns width and such, it will make the container(of only the grid) horizontally scroll. Is there some way around this?
autowidth: true from 3.5 onwards
It works for me:
width: null,
shrinkToFit: false,
I'm using this to set the width of the grid to the width of the parent container.
function resizeGrid() {
var $grid = $("#list"),
newWidth = $grid.closest(".ui-jqgrid").parent().width();
$grid.jqGrid("setGridWidth", newWidth, true);
}
I ended up using the jqGrids.fluid extension to do this and it worked great.
UPDATE: That link seems to be dead, but the archived article can be viewed here.
You can try to fix the width of jqGrid with respect of a function which I described here Correctly calling setGridWidth on a jqGrid inside a jQueryUI Dialog
wonderful function for this i found here (stackoverflow) cannot remember the post. I have the height portion commented out keep that in mind (was not working for me) but the width is perfect. throw this anywhere in your php file.
$resize = <<<RESIZE
jQuery(window).resize(function(){
gridId = "grid";
gridParentWidth = $('#gbox_' + gridId).parent().width();
$('#' + gridId).jqGrid('setGridWidth',gridParentWidth);
// $('#' + gridId).jqGrid('setGridHeight', //Math.min(500,parseInt(jQuery(".ui-jqgrid-btable").css('height'))));
})
RESIZE;
Try to set width to "null". It works for me.
$(grid).jqGrid({
width: null,
});
It looks like this is not supported. According to the docs for setGridWidth:
Sets a new width to the grid dynamically. The parameters are:
new_width is the new width in pixels...
The docs for the width option also do not mention being able to set width as a percentage.
That being said, you can use the autowidth feature or a similar technique to give the grid the correct initial width. Then follow the methods discussed in resize-jqgrid-when-browser-is-resized to ensure the grid is properly resized when the browser window is resized, which will simulate the effect of having 100% width.
loadComplete : function () {
$("#gridId").jqGrid('setGridWidth', $(window).width(), true);
},
Simpler
$("#gridId").setGridWidth($(window).width() );
Try this,
Replace width: 1100 to autowidth: true,
You can't give width in percent, while if you want according to screen resolution then set as follows:
var w = screen.width
and then use this variable in width option of jqgrid.
Hope it will useful.
I have done this and working like charm.
$(document).ready(function () {
$("#jQGridDemo").setGridWidth(window.innerWidth - offset);
});
I have put offset of 50
I've noticed that only the combination of all 3 answers given, i.e. JohnJohn's answer,
Bhargav's answer and
Molson's answer helped me to achive a real automatic resize.
So I have created some code that takes advantage of all, see the snippet below. I've also improved it so you can either pass a single grid object or an array of grids to be resized.
If you try it out ensure that you
click on Run code snippet, and
then click on the "Full page" link button in the upper right corner.
Resize the window and watch how the grids changes their size and re-align automatically:
// see: https://free-jqgrid.github.io/getting-started/
// CDN used: https://cdnjs.cloudflare.com/ajax/libs/free-jqgrid
$(function() {
// pass one single grid, or an array of grids
function resizeGrid(jqGridObj) {
var $gridArray = Array.isArray(jqGridObj) ? jqGridObj : [jqGridObj];
for(let i=0; i<$gridArray.length; i++) {
var $grid=$gridArray[i],
newWidth = $grid.closest(".ui-jqgrid").parent().width();
$grid.jqGrid("setGridWidth", newWidth, true);
}
};
// template for the 2 grids
function createGrid(gridName, gridData) {
var gridObj=$("#"+gridName); gridObj.jqGrid({
autowidth: true, height: 45,
colNames: ['First name', 'Last name', 'Updated?'],
colModel: [{name: "firstName"}, {name: "lastName"}, {name: "updated"}],
data: gridData,
loadComplete: function() {
// resize on load
resizeGrid(gridObj);
}
});
return gridObj;
}
// instantiate Grid1
var data1 = [
{ id: 10, firstName: "Jane", lastName: "Doe", updated: "no"},
{ id: 20, firstName: "Justin", lastName: "Time", updated: "no" }
];
var gridObj1=createGrid("grid1", data1);
// instantiate Grid2
var data2 = [
{ id: 10, firstName: "Jane", lastName: "Smith", updated: "no"},
{ id: 20, firstName: "Obi-Wan", lastName: "Kenobi", updated: "no" }
];
var gridObj2=createGrid("grid2", data2);
function debounce(fn, delay) {
delay || (delay = 200);
var timer = null;
return function () {
var context = this, args = arguments;
clearTimeout(timer);
timer = setTimeout(function () {
fn.apply(context, args);
}, delay);
};
}
function throttle(fn, threshhold, scope) {
threshhold || (threshhold = 200);
var last,
deferTimer;
return function () {
var context = scope || this;
var now = +new Date,
args = arguments;
if (last && now < last + threshhold) {
// hold on to it
clearTimeout(deferTimer);
deferTimer = setTimeout(function () {
last = now;
fn.apply(context, args);
}, threshhold);
} else {
last = now;
fn.apply(context, args);
}
};
}
// change size with window for both grids
jQuery(window).resize(throttle(function(){
resizeGrid([gridObj1, gridObj2]);
}));
});
<meta charset="utf-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<title>Resizing jqGrid example</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.11.4/themes/redmond/jquery-ui.min.css"/>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/free-jqgrid/4.15.2/css/ui.jqgrid.min.css"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/free-jqgrid/4.15.2/jquery.jqgrid.min.js"></script>
<table id="grid1"></table>
<br/>
<table id="grid2"></table>
Note: While this example is simple, if you have more complex jqGrids you will need throttling or debouncing (the 2 functions throttle and debounce are taken from there), otherwise the resize event could be really slow. Follow the link to read more about it. I prefer throttling in this case because it looks smoother, but I have included both functions so you can use them if needed in your code.
In my real code I needed throttling, otherwise resizing was getting far too slow. The code snippet already includes a throttled handler with a default threshold of 200ms. You can experiment with it, for example if you replace throttle by debounce in the code snippet, i.e.
jQuery(window).resize(debounce(function(){
resizeGrid([gridObj1, gridObj2]);
}));

Categories