How to test react konva from cypress? - cypress

I have rectangles as screens on canvas using react-konva. How to test clicking the screens rectangle on testing tools like cypress that uses DOM Element to select the target element?
I am seeing that this is impossible, unless by creating screens DOM Element for testing purpose apart of what currently exists on the canvas. Somehow this will take a lot of time and cumbersome too.
So I wonder if we have a way to work around this to test objects that are drawn inside canvas itself?

Take a look into Konva testing code. Like https://github.com/konvajs/konva/blob/master/test/functional/MouseEvents-test.js
You can emulate clicks with this code (from here):
Konva.Stage.prototype.simulateMouseDown = function(pos) {
var top = this.content.getBoundingClientRect().top;
this._mousedown({
clientX: pos.x,
clientY: pos.y + top,
button: pos.button || 0
});
};
// the use it:
stage.simulateMouseDown({ x: 10, y: 50 });
But you have to find a way to access the stage instance for such testing. And I am not sure it is good in a cypress way, because its API is abstract and DOM-based.
Or you can try to trigger events with cypress:
cy.get(`.container > div`)
.trigger('mousedown', { clientX: x, clientY: y })

Related

Plotly.js, show tooltips outside of chart container

I need to implement a plotly.js chart on a page with a very restricted width. As a result, a tooltip is partially cut. Is it possible to cause tooltip not to be limited by plotly.js container size?
My code example at codepen: https://codepen.io/anatoly314/pen/gOavXzZ?editors=1111
//my single trace defined as following but it's better to see example at codepen
const yValue1 = [1000];
const trace1 = {
x: [1],
y: yValue1,
name: `Model 1`,
text: yValue1.map(value => Math.abs(value)),
type: 'bar',
textposition: 'outside'
};
It is, by design, not possible for any part of the chart to overflow its container.
I would say it is wrong to say that by design this is not possible! It is a bit hacky, but when you add the following lines, it shows the label outside of svg:
svg.main-svg,svg.main-svg *
{
overflow:visible !important;
}
The answer given by rokdd works. However the css selector should be more specific, otherwise it's natural that you will introduce subtle bugs (particularly if you need to scroll the content where the plotly chart is contained).
If we look at the DOM tree constructed by Plotly, we find that the tooltips are created inside the <g class="hoverlayer"></g> element (which is a direct child of one of the three <svg class="main-svg"></svg>). So that parent (that svg.main-svg element) is only one that needs to affected.
The ideal css selector in this case would be the :has selector. However it's still not supported (as of 2022): https://css-tricks.com/the-css-has-selector/
So the next simplest thing is to use a little bit of javascript right after we call Plotly.newPlot:
// get the correct svg element
var mainSvgEl = document.querySelector('#positive g.hoverlayer').parentElement;
mainSvgEl.style['overflow'] = 'visible';
Or in a more generic way (works for any chart):
Array.from(document.querySelectorAll('g.hoverlayer')).forEach(hoverEl => {
let mainSvgEl = hoverEl.parentElement;
mainSvgEl.style['overflow'] = 'visible';
});

trigger built-in javascript animations provided by Divi theme when content becomes visible in the browser

I build WordPress sites using the Divi theme from Elegant Themes.
This theme provides a lot of visual modules to build your pages, and some of these modules have built-in animations.
For instance, the circle counter module displays a number with an animated circle around it, a percentage of the circle being colored based on the number displayed within the circle.
The animation plays when you scroll the page and when the circle counter module becomes visible in the browser.
I would like to know if I can use the browser development tools, and how, to find out how the animation is played, so I can trigger it whenever I want from my own scripts.
I also have access to the source code of the theme, but I don't know how to start to find what I am looking for.
And Divi support says "I am afraid that this feature is not supported. It would require customization which goes beyond the level of support that we can provide here.", so this is why I am here.
The circle version below will update during page load but the gauge doesn't adjust after that - only the number value changes. Passing a 'newval' to a progress bar will step up or down as necessary.
$(".et_pb_circle_counter_0").animate({
'data-width': newval
},{
duration: 1000,
specialEasing: {
width: 'linear'
},
step: function (now) {
$(".et_pb_circle_counter_0 .et_pb_circle_counter_inner").attr("data-number-value", newval );
$(".et_pb_circle_counter_0 span.percent-value").html( Math.ceil(now) );
}
});
// Progress Bars
$(".et_pb_counter_0 span.et_pb_counter_amount").animate({
width: newval+"%"
},{
duration: 1500,
specialEasing: {
width: 'linear'
},
step: function (now) {
$(".et_pb_counter_0 span.et_pb_counter_amount").attr("data-width", Math.ceil(now) + "%");
$(".et_pb_counter_0 span.et_pb_counter_amount_number").attr("data-width", Math.ceil(now) + "%");
$(".et_pb_counter_0 span.et_pb_counter_amount_number").html(Math.ceil(now) + "%");
}
});

Svelte and D3 brush

I'm struggling to understand how to use Svelte with something like D3's brush project. Svelte operates using a declarative approach. In the area chart example the SVG for lines is written out in the template HTML. To do this with D3 you would use Javascript function calls to select an element and call another function to modify the DOM. In the aforementioned chart example the D3 scaled library is only used to generate the axis array but the HTML itself is managed by Svelte. It makes sense Svelte works this way - building things up with function calls would be a lot less clean, but I can't figure out how to do this with the brush. How can I declaratively build up the brush HTML inside of my Svelte template, and how would this affect things like brush events? Would it rather be best to just use the brush functions inside of say onMount and sort-of tie change events to local Svelte variables?
The same problem exists in React, because both React and D3 want to be in charge of the DOM. In React you simply call the a function that instructs D3 to do it's work in the ComponentDidMount method (or a useEffect if using hooks.
Svelte expects to be in charge of the situation, you declare how the UI is constructed, and define the operations, leaving it to do the work. It won't be able to track what D3 does, so I suspect you need to just let D3 be in charge of that part, and not worry about it being a little bit hacky.
I managed to do this myself https://svelte.dev/repl/00f726facd434b978c737af2698e0dbc?version=3.12.1
As Mikkel said above, the way Svelte is designed doesn't play well naturally with something like D3. As I see it you have two options: try to wire D3 events into Svelte's reactive variables, or try to implement the functionality yourself.
I opted for the second version. I took the HTML and CSS that D3 Brush created, adding a mouse handler to the carets, and tied all the variables together reactively. (The last part I did very messily. Would appreciate any feedback on doing this cleaner from other Svelte users).
It also took me a bit to wrap my head around this. But in the end it's actually not that complicated. They key step is to decide which library takes care of which responsibility.
D3 simply overlaps with Svelte since it also renders things on the screen. But if you think about it, you don't really need a renderer if you already have Svelte. Once you have a renderer, the complicated part about charts is really the positioning. And this is where D3 really shines. If you "cherry pick" the best from both worlds you actually end up with a great dev experience.
But, alas, you can also leave the rendering to D3. But then you need to keep Svelte out of the picture as much as possible.
Essentially you have two options that both work well:
Svelte renders only the container DOM and then hands over to D3 to both calculate & render. You interface between both worlds only during onMount and onDestroy
Svelte renders the whole DOM, D3 provides the chart positions.
As for the brush functionality:
I found it to work best to create a ChartContainer (which is essentially just an SVG) with slots and then drop a Brush component inside.
<script>
import { createEventDispatcher } from "svelte";
export let minX;
export let maxX;
export let dX = 0;
export let height;
const dispatch = createEventDispatcher();
let startX,
endX,
mouseDown = false,
brushArea;
function onMouseDown(event) {
if (mouseDown) return;
mouseDown = true;
brushArea.removeEventListener("mousemove", onMouseMove);
brushArea.removeEventListener("mouseup", onMouseUp);
brushArea.addEventListener("mousemove", onMouseMove);
brushArea.addEventListener("mouseup", onMouseUp);
brushArea.style.cursor = "ew-resize";
startX = Math.max(event.offsetX - dX, minX);
endX = null;
}
function onMouseMove(event) {
endX = Math.min(event.offsetX - dX, maxX);
}
function onMouseUp(event) {
mouseDown = false;
if (!endX) startX = null;
brushArea.style.cursor = null;
brushArea.removeEventListener("mousemove", onMouseMove);
brushArea.removeEventListener("mouseup", onMouseUp);
const active = !!startX;
dispatch("brush", {active, startX, endX, clear});
}
function clear() {
startX = null;
endX = null;
}
</script>
<rect class="ui--chart__brush_area"
bind:this={brushArea}
x={minX}
y="0"
height={height}
width={maxX-minX}
on:mousedown={onMouseDown}
/>
{#if endX != null}
<rect class="ui--chart__brush"
x={startX < endX ? startX : endX}
y="0"
height={height}
width={startX < endX ? endX-startX : startX-endX}
/>
{/if}
The dX prop is used to consider a left margin. Might or might not be required in your use case (depending on how you setup your chart). The key thing is to be able to use offsetX from the mouse event so that you know how far off from the SVG borders your mouse events have fired.
Then, you just
listen for the brush events
extract the coordinates
convert them to values -by using yourScale.invert(coordinate)
use these values, e.g. to update your chart
Like so:
function onBrush(event) {
const {active, startX, endX, clear} = event.detail;
if (active) {
const startDate = scaleX.invert(startX);
const endDate = scaleX.invert(endX);
dispatch("brush", {active, startX, endX, startDate, endDate, clear});
}
}
Hope this helps anyone else struggling with this. Good luck!
There isn't a single answer to your question, but I think the best option is to render the "most relevant data" using svelte's html, leaving the interactive elements (like brush) to run only on client side.
You should known that svelte converts your html to js generators internally, so calling the d3 functions is actually pretty similar to what svelte does client side. The only real advantage of using the svelte's html instead of d3 functions calls is SSR, and that's the reason it's reasonable to leave the brush to be client side only (since it needs js to be interactive anyway).
Svelte is kind of a "reactive vanilla", so you can use low-level libraries almost directly. Sometimes you need to do some tricks to access DOM elements directly (like d3 usually does), for that I recommend using the bind:this directive. Example:
<script>
import { brushX, select } from 'd3';
//...
let brushElement;
$: brush = brushX()
.extent([[padding.left, padding.top], [width - padding.right, height - padding.bottom]])
.on('end', onZoom)
$: if (brushElement) {
select(brushElement)
.call(brush)
}
</script>
<svg>
...
<g bind:this={brushElement} width={...} height={...} />
...
</svg>
One thing to consider when using DOM's API is SSR (sapper), thus any call of d3's select should be done only in the browser. The if case in the code above takes care of that, because the bind:this directive will only set brushElement when running client side.

Performance considerations using html elements on top of canvas

It is known that if you have html elements (for example a modal window with lists) on top of a flash element you have huge performance issues cause flash cause the browser to repaint the any html on top of it while the flash is animating. I wonder if the same happens if you have html elements on top of an animating canvas element.
I am asking this cause I am building a canvas game and I wonder if it is a good idea to make the GUI (menus, navigation buttons etc) using DOM and not drawing it on canvas.
I just tested using Chromium Version 28.0.1500.45 Mageia.Org 3 (205727) and the elements are NOT repainted while my canvas animates.
I tried this simple box animation with a DIV over it. After that, I profiled my application to see what was happening. I noticed only the canvas was being repainted.
window.requestAnimFrame = (function(callback) {
return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame ||
function(callback) {
window.setTimeout(callback, 1000 / 60);
};
})();
var GX = 0;
function animate() {
var canvas = document.getElementById('jaja');
var context = canvas.getContext('2d');
// update
GX += 10;
if (GX > 500) GX = 0;
// clear
context.clearRect(0, 0, canvas.width, canvas.height);
// draw stuff
context.beginPath();
context.rect(GX, 10, 100, 100);
context.fillStyle = '#8ED6FF';
context.fill();
context.lineWidth = 1;
context.strokeStyle = 'black';
context.stroke();
requestAnimFrame(function() {
animate();
});
} // request new frame
Following, I tried to make the DIV repaint by selecting the text. This time yes, the DIV repainted.
Here's a screenshot of the profile.
1: We can se the Paint (600x586) being called everytime I do my animation.
2: It called Paint for the DIV element ONLY when I selected the text.
I personally do not believe any other browser would have a different behavior than that.
Yes, putting other DOM elements on top of a canvas element will reduce its performance.
This is because the browser have to do extra clipping when updating canvas / painting.
The canvas need to update 60 times per second to output to screen. If something is on top it needs to be clipped just as many times. If the DOM element is repainted as well will be browser dependent but the performance of the canvas element itself is reduced.
Usually the DOM paints happens in a single thread (but is about to change for most major browsers) so if there is extra load on that thread it will affect everything else being drawn too.
And there is the single-threading of JavaScript which is necessary to use to update canvas. If canvas has reduced performance than the script executing its changes (as well as changes to the DOM) will get hit too.

FabricJS Canvas, Scrolling parent container moves child hit area

I am using FabricJS to create an application. I am finding that scrolling a parent div/container offsets the selectable area of an object to the right in direct relation to amount scrolled.
So, if I have a canvas that is 1200x600 and a container div that is 600x600 and I add a rect to that canvas at 400, 120; when I scroll 200px, I can't click on the rect to select it. Rather, I have to move my mouse to 600, 120 (empty space) to get the cross bar and select the rect.
Not sure if this is known, or has a work around - but I would appreciate any help possible.
You'll have to modify FabricJs code to make it work.
The problem is in the getPointer function, if you search for it in all.js you'll notice the comment "this method needs fixing" from kangax.
A workaround can be substituting this function with
function getPointer(event) {
// TODO (kangax): this method needs fixing
return { x: pointerX(event) + document.getElementById("container").scrollLeft, y: pointerY(event) + document.getElementById("container").scrollTop };
}
where "container" is the wrapper div of you canvas. It's not nice, since you have to put the exact id, but it works.
Hope this helps.

Resources