Angular Animations - Dynamic and Responsive translations - angular5

I have a component that is usually dormant (by which I simply mean it is of little interest to the user), but in a certain state this component becomes 'activated' and I want to put it in the exact center of the screen and enlarge it to grab the user's attention.
There are several of these components in the dormant state, but only ever 1 activated. The dormant components could be anywhere on the screen, so I wanted a solution that would translate the component from wherever it was originally to the middle of the screen while activated, and then return it back to its original dormant position when done.
Attempting to do this:
template:
<div #myElement [#isActivated]="activated">
Hello Stack Overflow
<button (click)="activated = activated === 'activated' ? 'dormant' : 'activated">
Toggle
</button>
</div>
typescript:
#Component({
// ...
animations: [
trigger('isActivated', [
state('dormant', style($transitionToActivated)),
state('activated', style({
transform: 'translateX(0%) translateY(0%) scale(1)'
})),
transition('dormant => activated', animate('1000ms ease')),
transition('activated => dormant', animate('1000ms ease'))
])
]
})
export class MyComponent implements OnInit {
#ViewChild('myElement') myElement: ElementRef;
activated = 'dormant';
transitionToActivated: any;
ngOnInit() {
let rect = this.myElement.nativeElement.getBoundingClinetRect();
this.transitionToActivated = {
transform: ''translateX(' + ((window.screen.width / 2) - (rect.right + rect.left) / 2) + ') translateY(' +
((window.screen.height / 2) - (rect.top + rect.bottom) / 2) + ') scale(1.5)'
}
}
}
My syntax here is off: the $transitionToActivated inside of the Component decorator is invalid. Is it possible to this kind of responsive animations with Angular Animations? Or will I need to look into a pure CSS solution?
[here's a plunker of what I'm trying... currently my attempt to put it in the exact center is commented out, and just some static animation instructions]

I figured out a couple things.
First, above I'm using window.screen for width and height of the 'screen.' This is actually giving me the resolution of the monitor (resizing the window doesn't affect it). I wanted document.documentElement to get the size of the viewport.
Second, I solved the issue of dynamic animations by using the AnimationPlayer to define the animations programmatically [rather than defining them in the Component decorator as I was trying to above].
I'm still curious as to whether the animations can be dynamically changed via the animation property inside the component decorator... I expect there must be a way, but I've been rather frustrated by the hand-wavy-ness of the Angular animations API and still can't figure it out.
Also, my solution acts funky when the viewport size is changed while in the 'activated state' (doesn't respond to resizing [as would be expected] and jumps at the start of its 'return' animation to the new middle of the viewport [again as expected].
Here's code and plunker to my solution:
export class App implements OnInit {
#ViewChild('myElement') myElement: ElementRef;
activated: BehaviorSubject<string> = new BehaviorSubject<string>('dormant');
transitionToActivated: any;
player: AnimationPlayer;
factory: any;
constructor(private builder: AnimationBuilder) {}
ngOnInit() {
console.log('viewport width: ' + document.documentElement.clientWidth);
console.log('viewport height: ' + document.documentElement.clientHeight);
let rect = this.myElement.nativeElement.getBoundingClientRect();
console.log('rect right: ' + rect.right);
console.log('rect left: ' + rect.left);
this.transitionToActivated = 'translateX(' + ((document.documentElement.clientWidth / 2) -
(rect.right + rect.left) / 2) + 'px) translateY(' +
((document.documentElement.clientHeight / 2) - (rect.top + rect.bottom) / 2) +
'px) scale(1)';
this.activated.subscribe(newValue => {
this.transitionToActivated = 'translateX(' + ((document.documentElement.clientWidth / 2) -
(rect.right + rect.left) / 2) + 'px) translateY(' +
((document.documentElement.clientHeight / 2) - (rect.top + rect.bottom) / 2) +
'px) scale(1)';
console.log(this.transitionToActivated);
if(newValue === 'activated'){
this.factory = this.builder.build([
style({ transform: 'translateX(0) translateY(0) scale(1)' }),
animate(
'1000ms',
style({ transform: this.transitionToActivated })
)
]);
this.player = this.factory.create(this.myElement.nativeElement, {});
this.player.play()
} else if(newValue === 'dormant'){
this.factory = this.builder.build([
style({ transform: this.transitionToActivated })
animate(
'1000ms',
style({ transform: 'translateX(0) translateY(0) scale(1)' }),
)
]);
this.player = this.factory.create(this.myElement.nativeElement, {});
this.player.play()
}
})
}
}

Related

D3 linkHorizontal() update on mouse position

I’m trying to implement a drag and drop functionality with a connection line. The connection line has a starting point ([x, y]), which is gathered when the mouse is clicked and a target point ([x, y]) which follows the mouse position and is continuously updated while dragging the element.
The project uses Vue.JS with VUEX store and for the connection line D3.js (linkHorizontal method https://bl.ocks.org/shivasj/b3fb218a556bc15e36ae3152d1c7ec25).
In the main component I have a div where the SVG is inserted:
<div id="svg_lines"></div>
In the main.js File I watch the current mouse position (targetPos), get the start position from the VUEX store (sourcePos) and pass it on to connectTheDots(sourcePos, targetPos).
new Vue({
router,
store,
render: (h) => h(App),
async created() {
window.document.addEventListener('dragover', (e) => {
e = e || window.event;
var dragX = e.pageX, dragY = e.pageY;
store.commit('setConnMousePos', {"x": dragX, "y": dragY});
let sourcePos = this.$store.getters.getConnStartPos;
let targetPos = this.$store.getters.getConnMousePos;
// draw the SVG line
connectTheDots(sourcePos, targetPos);
}, false)
},
}).$mount('#app');
The connectTheDots() function receives sourcePos and targetPos as arguments and should update the target position of D3 linkHorizontal. Here is what I have:
function connectTheDots(sourcePos, targetPos) {
const offset = 2;
const shapeCoords = [{
source: [sourcePos.y + offset, sourcePos.x + offset],
target: [targetPos.y + offset, targetPos.x + offset],
}];
let svg = d3.select('#svg_lines')
.append('svg')
.attr('class', 'test_svgs');
let link = d3.linkHorizontal()
.source((d) => [d.source[1], d.source[0]])
.target((d) => [d.target[1], d.target[0]]);
function render() {
let path = svg.selectAll('path').data(shapeCoords)
path.attr('d', function (d) {
return link(d) + 'Z'
})
path.enter().append('svg:path').attr('d', function (d) {
return link(d) + 'Z'
})
path.exit().remove()
}
render();
}
I stole/modified the code from this post How to update an svg path with d3.js, but can’t get it to work properly.
Instead of updating the path, the function just keeps adding SVGs. See attached images:
Web app: Multiple SVGs are drawn
Console: Multiple SVGs are added to element
What am I missing here?
#BKalra helped me solve this.
This line keeps appending new SVGs:
let svg = d3.select('#svg_lines') .append('svg') .attr('class', 'test_svgs');
So I removed it from the connectTheDots() function.
Here is my solution:
In the main component I added an SVG with a path:
<div id="svg_line_wrapper">
<svg class="svg_line_style">
<path
d="M574,520C574,520,574,520,574,520Z"
></path>
</svg>
</div>
In the connectTheDots() function I don't need to append anymore, I just grap the SVG and update its path:
function connectTheDots(sourcePos, targetPos) {
const offset = 2;
const data = [{
source: [sourcePos.y + offset, sourcePos.x + offset],
target: [targetPos.y + offset, targetPos.x + offset],
}];
let link = d3.linkHorizontal()
.source((d) => [d.source[1], d.source[0]])
.target((d) => [d.target[1], d.target[0]]);
d3.select('#svg_line_wrapper')
.selectAll('path')
.data(data)
.join('path')
.attr('d', link)
.classed('link', true)
}

How to automatically change the width of a preview edge according to the zoom level in cytoscape.js-edgehandles?

I am using cytoscape.js and the edgehandles extension (https://github.com/cytoscape/cytoscape.js-edgehandles).
Currently I am automatically resizing the line width of my edge using the event 'zoom':
cy.on('zoom', function (evt) {
let zoomFactor = cy.zoom()
let newFontSize = defaut_font_size.general * 1 / zoomFactor;
cy.filter("node[type='main-WP'][parent !^= 'compound']").style({
"font-size": newFontSize,
});
cy.edges().style({
'width': (0.3 * newFontSize),
});
});
How can I achieve something similar on the classes (.eh-preview, .eh-ghost-edge)?
You can give a function for a style. The drawback is the functions are not JSON serializable. So If you need to import/export a graph with cy.json() you should also apply the function styles.
cy.style().selector('edge.eh-preview')
.style({
'width': () => {
let zoomFactor = cy.zoom()
let newFontSize = defaut_font_size.general * 1 / zoomFactor;
return (0.3 * newFontSize);
},
}).update();

Refreshing Vuetify V-Calendar with new events hide the events in the "Month" view

Currently developing an appointment-making application using a C# API in Vue.js with Vuetify, I encounter a behaviour with the component V-Calendar I can't comprehend. When originally feeding events to the calendar (appointments retrieved from a database by contacting the API), those events are correctly displayed as followed :
Original calendar loading
The query originally ignores cancelled appointments. However, I give the option to include them with a checkbox in the calendar header. Checking the box automatically refreshes the list of events through a watcher. When doing so, the calendar has a strange behaviour and does no longer display the events. This only occurs in the "Month" view, the "Day" and "Week" ones correctly display the data.
Result of refreshing the calendar
Here is the definition of my calendar (programming in french, translated in english the variables/methods for your easier understanding)
<v-calendar ref="calendar"
v-model="focus"
:event-color="getEventColor"
:events="events"
:first-interval="13"
:interval-count="22"
:interval-format="intervalFormat"
:interval-minutes="30"
:type="type"
:weekdays="weekDays"
color="primary"
event-more-text="Show more"
event-overlap-mode="column"
locale="fr"
#change="updateRange"
#click:event="showEvent"
#click:more="viewDay"
#click:date="viewDay">
<template #event="event">
<div v-if="event.eventParsed.input.idEtat === etats.annule"><s><i>{{
event.eventParsed.input.name
}}</i></s></div>
<div>{{ event.eventParsed.input.name }}</div>
</template>
</v-calendar>
The definition of the updateRange method (called once when the page is loaded in the created() hook)
async updateRange({start, end}) {
this.currentDateDebut = start.date;
this.currentDateFin = end.date;
await this.refreshCalendarData();
}
The definition of the refreshCalendar method
async refreshCalendarData() {
this.loading = true;
const events = []
//Récupération des rendez-vous
await this.getRendezVous(this.currentDateDebut, this.currentDateFin);
this.rendezVous = await this.$store.getters["rendezVous/getRendezVousData"];
for (let i = 0; i < this.rendezVous.length; i++) {
const calculImcPossible = (this.rendezVous[i].taille != null && this.rendezVous[i].taille > 0) &&
(this.rendezVous[i].poids != null && this.rendezVous[i].poids > 0);
const calculImc = calculImcPossible
? (Math.round(this.rendezVous[i].poids / ((this.rendezVous[i].taille / 100) * (this.rendezVous[i].taille / 100)) * 100) / 100).toFixed(2)
: null;
const libelleImc = this.getLibelleImc(calculImc);
events.push({
id: this.rendezVous[i].id,
idInstitution: this.rendezVous[i].idInstitution,
name: this.heureCourte(this.rendezVous[i].date) + " | Appointment",
start: new Date(this.rendezVous[i].date),
end: new Date(new Date(this.rendezVous[i].date).getTime() + 15 * 60000),
color: this.rendezVous[i].institution.color,
timed: true,
taille: this.rendezVous[i].taille != null && this.rendezVous[i].taille > 0
? this.rendezVous[i].taille + "cm"
: "indéfinie",
poids: this.rendezVous[i].poids != null && this.rendezVous[i].poids > 0
? this.rendezVous[i].poids + "kg"
: "indéfini",
sexe: this.rendezVous[i].patient.sexe,
imc: calculImc != null ? (calculImc + " (" + libelleImc + ")") : "non-déterminé",
nom: this.rendezVous[i].patient.nom + " " + this.rendezVous[i].patient.prenom,
telephone: this.rendezVous[i].patient.telephone != null ? this.rendezVous[i].patient.telephone : "-",
email: this.rendezVous[i].patient.email != null ? this.rendezVous[i].patient.email : "-",
commentaire: this.rendezVous[i].commentaire,
regime: this.rendezVous[i].regime,
hospitalisation: this.rendezVous[i].hospitalisation,
contagieux: this.rendezVous[i].contagieux,
incontinent: this.rendezVous[i].incontinent,
naissance: this.dateCourte(this.rendezVous[i].patient.naissance),
diabete: this.rendezVous[i].diabete.type,
examen: this.rendezVous[i].examen.nom,
idEtat: this.rendezVous[i].idEtat,
idPatient: this.rendezVous[i].idPatient,
typeEvent: "rendez-vous",
editable: this.rendezVous[i].editable
});
}
}
And finally, the definition of the watcher showCancalledAppointments
async showCancelledAppointments() {
await this.refreshCalendarData();
}
Do you have any idea why this behaviour is displayed by the calendar ? Thank you for your time and help.
Updating the solution with the command 'npm update' fixed the problem. The latest version of Vuetify seems to solve the issue

Vue.Js draw lines between components problem

I am using SVGs to draw lines between components that are related in my app. Currently I am grabbing those elements and getting their position info with document.getElementById() and then using getClientBoundingRect.
This generally works, but there is occasional render wonkiness.
Is there a better way to do this? Perhaps an already existing library that works with VueJs?
Changing the class puts a CSS filter on the shape because you have
.isSelected {
filter: brightness(50%);
}
Now the W3C Filter Effects spec says
The application of the ‘filter’ property to an element formatted with the CSS box model establishes a new stacking context the same way that CSS ‘opacity’ does, and all the element's descendants are rendered together as a group with the filter effect applied to the group as a whole.
So browsers are doing the correct thing per that specification. The new stacking context puts the shape in front of the line.
See also this wontfixed Chromium bug
The problem ended up being because of document.getElementById and z-order
I ended up changing my timer to get the element by ref, and iterate through the children of my component to find it.
Here is the code:
drawLines: function () {
let children = this.$children
let lines = this.lines
lines.splice(0, lines.length)
let scheduleContainer = this.$refs.scheduleContainer
let scheduleContainerRect = scheduleContainer.getBoundingClientRect();
for (let i = 0; i < children.length; i++) {
let child = children[i]
if (child.$props.assignment) {
if (child.$props.assignment.assignmentRequestId != "00000000-0000-0000-0000-000000000000") {
for (let ii = 0; ii < children.length; ii++) {
let child2 = children[ii]
if (child2.$props.assignmentRequest) {
if (child2.$props.assignmentRequest.id == child.$props.assignment.assignmentRequestId) {
let assignmentRect = child.$refs.theContainer.getBoundingClientRect()
let requestRect = child2.$refs.theContainer.getBoundingClientRect()
let x1 = ((assignmentRect.left - scheduleContainerRect.left) + 12.5) + 'px'
let y1 = ((assignmentRect.top - scheduleContainerRect.top) + 12.5) + 'px'
let x2 = ((requestRect.left - scheduleContainerRect.left) + 12.5) + 'px'
let y2 = ((requestRect.top - scheduleContainerRect.top) + 12.5) + 'px'
let line = { 'x1': x1, 'y1': y1, 'x2': x2, 'y2': y2 }
lines.push(line)
}
}
}
}
}
}
},

Datatables - fixedHeader with scrollX

I am trying to use Datatables with fixedheader (v3) as well as enable horizontal scrolling. Attached is the fiddle http://jsfiddle.net/xF8hZ/344/
$(document).ready(function() {
var table = $('#example').DataTable({
searching: false,
paging: false,
ordering: false,
info: false,
fixedHeader: true,
scrollX: true
});
} );
.
When scrolling the fixedheader width doesn't align with the rest of the table. Can you help me solve this please?
Thanks
Pure css solution using css sticky (not work in ie 11):
remove the fixHeader plugin
add this css
.dataTables_scrollHead {
position: sticky !important;
top: 119px;
z-index: 99;
background-color: white;
box-shadow: 0px 5px 5px 0px rgba(82, 63, 105, 0.08);
}
I have read for 2 days about this, so joining all of them together, here's my contribution.
I got it figured out, hopefully this is useful for someone or help in the development as well.
My datatables is in a DIV and horizontal Scrolling enable due to huge table. When fixed header was set it was set as FIXED, and a new table is inserted at the BODY rather than inside the div.
I made it appended to the DIV instead of BODY so that the overflow rule might be inherited.
File:
dataTables.fixedHeader.min.js (search for "appendTo")
From:
e.table().node().cloneNode(!1)).removeAttr("id").append(f).appendTo("body")
To:
e.table().node().cloneNode(!1)).removeAttr("id").append(f).appendTo(".dataTables_scroll")
Now that it's appended to the the datatables-created-div, same level as dataTables_scrollHead, dataTables_scrollBody rather than stranded alone at body, whatever overflow still showing/sticking out.
File:
fixedHeader.bootstrap.min.css
From:
table.dataTable.fixedHeader-floating{position:fixed !important}
To
table.dataTable.fixedHeader-floating{position:absolute !important}
or File:
fixedHeader.dataTables.min.css
From:
table.fixedHeader-floating{position:fixed !important;background-color:white;}
To
table.fixedHeader-floating{position:absolute !important;background-color:white;}
Careful of CACHE of the CSS and JS files.
Now that the floating sticky row has appeared but out of place and overflow in effect.
Have this JS running, detecting when fixedHeader-floating appears, keep adjusting them to follow the horizontal scroll and stick to the top.
setInterval(function(){
if($('.fixedHeader-floating').is(':visible')){
var myoffset = Math.round($(window).scrollTop() - $('#Detail2Container').position().top + $('.topbar').height() - 145);
var positionleft = $('.dataTables_scrollHeadInner').position();
$('.fixedHeader-floating').css({ 'top': myoffset, 'left': positionleft.left + 10 });
}
}, 50); //every 50ms
Detail2Container is the DIV that wrap the Datatables.
I couldn't use dataTables_wrapper as reference as there are a few of them in the same page. In my page, I only one table that needs fixedHeader, if I need 2, it will be tough. But I will deal with it when the needs arise.
You could adjust the calculation according to your own design.
2 days for me to figure this out. So I feel like sharing it too.
I found a solution on my project by doing this:
$('#example').scroll(function() {
if ( $(".fixedHeader-floating").is(":visible") ) {
$(".fixedHeader-floating").scrollLeft( $(this).scrollLeft() );
}
});
DataTables creates a new table as the fixedHeader when you scroll down, what I'm doing here is to detect when the user scrolls horizontally on the $('#example') table and then I use scrollLeft() on the fixedHeader to match the scroll position.
I also added this to my .css so the user won't be able to scroll on the fixedHeader table:
.fixedHeader-floating {
overflow: hidden;
}
Following is working to me
$('.dataTables_scrollBody').on('scroll', function () {
$('.dataTables_scrollHead', $(this).parent()).scrollLeft($(this).scrollLeft());
});
This fixed the problem.
let tableParams = {
autoWidth: false,
// etc...
scrollX: true,
fixedHeader: true,
initComplete: function(settings, json) {
// To fix the issue of when scrolling on the X axis, the header needs also to scroll as well.
this.find('.dataTables_scrollBody').on('scroll', function() {
this.find('.dataTables_scrollHeadInner').scrollLeft($(this).scrollLeft());
});
},
};
Also to hide the vertical scroll bar.
me.containerElement.find('.dataTables_scrollBody').css({'overflow-y': 'hidden'});
where containerElement is the parent element of the datatable element.
Based on this, I was able to make it working (issue: when FixedHeader is floating, sorting would not work ==> see update 1 below to fix it)
Explanations:
FixedHeader (.dataTables_scrollHeadInner) is a different table outside of datatable (.dataTables_scrollBody)
when scrolling vertically, it will check scrolltop and set FixedHeader top accordingly.
when scrolling horizontally, it will scroll FixedHeader with body ($('.dataTables_scrollHeadInner').scrollLeft($(this).scrollLeft()))
JS
// sorry - had to use global variable
// global variable for scroll-body y position
var yPositionOfScrollBody;
function adjustDatatableInnerBodyPadding(){
let $dtScrollHeadInner = $('.dataTables_scrollHeadInner');
let outerHeightOfInnerHeader = $dtScrollHeadInner.outerHeight(true);
//console.log('outerHeightOfInnerHeader => ' + outerHeightOfInnerHeader);
$('.dataTables_scrollBody').css('padding-top', outerHeightOfInnerHeader);
}
function setFixedHeaderTop(header_pos){
//console.log("header_pos : " + header_pos);
$('.dataTables_scrollHeadInner').css({"top": header_pos});
}
function fixDatatableHeaderTopPosition(){
//console.log("fixHeaderTop...");
yPositionOfScrollBody = window.scrollY + document.querySelector('.dataTables_scrollBody').getBoundingClientRect().top;
//console.log("yPositionOfScrollBody: " + yPositionOfScrollBody);
setFixedHeaderTop(yPositionOfScrollBody);
}
function onDataTableInitComplete(settings, json) {
// for vertical scolling
yPositionOfScrollBody = window.scrollY + document.querySelector('.dataTables_scrollBody').getBoundingClientRect().top;
// datatable padding adjustment
adjustDatatableInnerBodyPadding();
// data table fixed header F5 (refresh/reload) fix
let scrollTop = window.pageYOffset || document.documentElement.scrollTop;
//console.log("scrollTop => " + scrollTop);
if(scrollTop > 1){
let header_pos;
if (scrollTop < yPositionOfScrollBody){
header_pos = yPositionOfScrollBody - scrollTop;
} else {
header_pos = 0;
}
setFixedHeaderTop(header_pos);
}
let $dtScrollHeadInner = $('.dataTables_scrollHeadInner');
// horizontal scrolling
$('.dataTables_scrollBody').on('scroll', function () {
let $dtScrollBody = $(this);
// synchronize
let amountOfLeftScroll = $dtScrollBody.scrollLeft();
$dtScrollHeadInner.scrollLeft(amountOfLeftScroll);
let scrollDiff = $dtScrollHeadInner.scrollLeft() - amountOfLeftScroll;
//console.log("scrollDiff: " + scrollDiff);
if(scrollDiff < 0){
$dtScrollHeadInner.css('left', scrollDiff);
}else{
//console.log("scroll back to left side");
$dtScrollHeadInner.css('left', '');
}
});
//console.log("adjusment mergin: " + yPositionScrollHeadInner);
$(document).on('scroll', function () {
let scroll_pos = $(this).scrollTop();
if(scroll_pos <= 0){
fixDatatableHeaderTopPosition();
}else{
let margin = yPositionOfScrollBody; // Adjust it to your needs
let cur_pos = $('.dataTables_scrollHeadInner').position();
let header_pos = cur_pos.top;
if (scroll_pos < margin){
header_pos = margin - scroll_pos;
} else {
header_pos = 0;
}
setFixedHeaderTop(header_pos);
}
});
}
$(function(){
$("#tableId").DataTable({
scrollX: true,
fixedHeader: true,
initComplete: onDataTableInitComplete,
// ... : ...
});
});
CSS
/* data table - scroll and fixed header */
table.dataTable.fixedHeader-floating {
display: none !important; /*Hide the fixedHeader since we dont need it*/
}
.dataTables_scrollHeadInner{
margin-left: 0px;
width: 100% !important;
position: fixed;
display: block;
overflow: hidden;
/*margin-right: 30px;*/
background: white;
z-index: 1;
}
.dataTables_scrollBody{
padding-top: 2.5em;
}
div.dataTables_scrollHead table.dataTable {
padding-right: 0;
}
Update 1 - sort issue fix
use fixedHeader: false
$(function(){
$("#tableId").DataTable({
scrollX: true,
fixedHeader: false,
initComplete: onDataTableInitComplete,
// ... : ...
});
});
You can change 'left' but saving initial value first:
var initLeft = 0;
$(".dataTables_scrollBody").scroll(function () {
if ($(".fixedHeader-floating").is(":visible")) {
if (initLeft == 0)
initLeft = $(".fixedHeader-floating").position().left;
$(".fixedHeader-floating").css("left", $(this).scrollLeft() * (-1) + initLeft);
}
});
$(document).ready(function() {
var table = $('#example').DataTable({
searching: true,
paging: true,
ordering: true,
info: false,
scrollY: 400,
dom: 'Blfrtip',
});
} );