custom tooltip opacity always 1 - reactjs

I have a custom tooltip in react-chartjs-2;
I found a solution of making custom tooltip, but my tooltip is always visible, in that solution tooltip hides when tooltip.opacity is 0, but in my case tooltip opacity is always 1, can smbd help me pls?
tooltips: {
enabled: false,
mode: 'x',
intersect: false,
custom: (tooltipModel) => {
if (tooltipModel.opacity === 0) {
// never called because opacity is always 1
this.hide();
return;
}
// const position = this.chartRef.current.chartInstance.canvas.getBoundingClientRect();
// set position of tooltip
// const left = position.left + tooltipModel.caretX;
// const top = position.top + tooltipModel.caretY;
// set values for display of data in the tooltip
const date = tooltipModel.dataPoints[0].xLabel;
const value = tooltipModel.dataPoints[0].yLabel;
// this.setPositionAndData(top, left, date, value);
},

Just to clarify for any other person who needs help on this.
When you create your handle for a custom toolbar, you need to pay attention to not overwrite the this(scope) object. Try with arrow function and remove the bind, the fn will get automatically the new scope and the opacity will be update when you move out from a chart bar/line etc.
The same issue will happen with the legend if you try to overwrite the onClick function using inline arrow function or functions. Some examples bellow.
_tooltip = (tooltipModel) => {
...
}
_legendOnClick = (ev) => {
ev.preventDefault()
}
render() {
const defaultOpt = {
legend: {
onClick: this._legendOnClick
},
tooltips: {
enabled: false,
custom: this._tooltip,
mode: 'index'
}
}
}
If you want to improve perfomance, you should remove the defaultOpt from the render method.
That's

Instead of this:
intersect: false,
custom: (tooltipModel) => {
if (tooltipModel.opacity === 0) {
use this workaround:
intersect: false,
custom: function(tooltipModel){
let hit=this._active[0].inRange(this._eventPosition.x, this._eventPosition.y);
if (tooltipModel.opacity === 0 || !hit) {
make sure not to use arrow function to keep 'this'.
In my case the root of the problem was that I defined the 'options' object in the render function of my component along with the 'custom' tooltip handler and called a setState from the inside of it - which caused an other render call immediately. This broke down the tooltip handling of chart.js. Moving the handler outside of the render function solved it without needing the above workaround.

Related

how to collapse objects in storybook controls

I searched through storybook's documentation for react and I can't seem to find how to make the object control for my argument appear collapsed by default instead of having to collapse it manually.
this is what I did
data: {
control: { type: 'object' },
table: { defaultValue: { summary: 'Object' } },
collapsed: true, // I want to find out if there is something like this
},
and this is what I was expecting
The option to configure this does not exist, unfortunately. Inspired by this comment regarding having the RAW-view open by default, I made a somewhat hacky solution, query all the spans at the top level for collapsing their content and trigger a click using JavaScript.
In the file manager.js add the following to make this work:
const observer = new MutationObserver(() => {
// Query the spans for collapsing objects.
const objectCollapseSpans = [...document.querySelectorAll('.rejt-tree > .rejt-object-node > span')];
// Query the spans for collapsing array of objects.
const arrayCollapseSpans = [...document.querySelectorAll('.rejt-tree > .rejt-array-node > span')];
const collapseSpans = [...arrayCollapseSpans, ...objectCollapseSpans];
for (const span of collapseSpans) {
if (span.className !== 'closed') {
span.click();
}
span.className = 'closed'
}
});
observer.observe(document.body, {
childList: true,
subtree: true,
})

Testcafe - how do I iterate over selectors of different element types?

I have a React address form which is config driven, and each element could either be a text <input> or a <select> dropdown. When trying to fill in the form using the code below, the text inputs are populated successfully, but the select element can't be found. If I remove the select elements from the loop and select them individually afterwards, it works fine. The MOCKS.deliveryAddress values are just strings.
const addressFields = {
addressLine2: Selector('[data-testid="input-addressLine2"]'),
addressLine1: Selector('[data-testid="input-addressLine1"]'),
addressLine3: Selector('[data-testid="input-addressLine3"]'),
addressLine4: Selector('[data-testid="input-addressLine4"]'),
postalCode: Selector('[data-testid="input-postalCode"]'),
};
const fieldConfig = {
addressLine1: 'text',
addressLine2: 'text',
addressLine3: 'text',
addressLine4: 'select',
postalCode: 'text',
};
const enterAddress = async () => {
await Promise.all(
Object.keys(addressFields).map(async (field) => {
if (fieldConfig[field] === 'text') {
if (MOCKS.deliveryAddress[field]) {
await t.typeText(
addressFields[field],
MOCKS.deliveryAddress[field],
{
replace: true,
},
);
}
} else {
await t.click(addressFields[field]);
await t.click(
addressFields[field]
.find('option')
.withText(MOCKS.deliveryAddress[field]),
);
}
}),
);
};
}
I get the error 1) The element that matches the specified selector is not visible.
Am I doing something wrong here in how I handle the selector inside a map?
Thanks in advance!
According to the TestCafe documentation, the element is considered as 'visible' if it does not have display: none or visibility: hidden CSS properties and has non-zero width and height. Using the browser development tool to investigate the properties of elements used in tests.
Turns out I was doing it wrong! I should have been chaining the click events for opening the dropdown and selecting an option, rather than awaiting the select click and then awaiting the option click. E.g.
await t
.click(addressFields[field])
.click(
addressFields[field]
.find('option')
.withText(MOCKS.deliveryAddress.addressLine4),
);

Fluent UI DetailsList - Is there a way to add filters to each column

I am using Fluent UI DetailsList. My table looks like below:
I need filters below every column (text or drop-down) as shown below:
Please let me know if this is possible? Or maybe a way to display custom header (using html) ?
This actually turned out to be easier than I thought it'd be...
If you're ok with clicking the column header to reveal the choices (vs having the dropdown directly under the title) then this can be achieved using the ContextualMenu component in conjunction with DetailsList. I got it working by tweaking from the variable row height example in the official docs: https://developer.microsoft.com/en-us/fluentui#/controls/web/detailslist/variablerowheights.
Add a ContextualMenu underneath your DetailsList:
<DetailsList
items={items}
columns={columns}
/>
{this.state.contextualMenuProps && <ContextualMenu {...this.state.contextualMenuProps} />}
Inside your column definition, set the hasDropdown action so the user gets a UI indicator that they can/should click the header, and call a contextMenu method (note I'm using onColumnContextMenu as well as onColumnClick so it doesn't matter if they left or right click the header:
{
key: 'dept',
name: 'Department',
fieldName: 'dept',
minWidth: 125,
maxWidth: 200,
onColumnContextMenu: (column, ev) => {
this.onColumnContextMenu(column, ev);
},
onColumnClick: (ev, column) => {
this.onColumnContextMenu(column, ev);
},
columnActionsMode: ColumnActionsMode.hasDropdown,
}
When the onColumnContextMenu method gets invoked, we need to build the context menu properties that will get consumed by the ContextualMenu component. Note the dismissal method as well, which clears out the state so the menu is hidden.
private onContextualMenuDismissed = (): void => {
this.setState({
contextualMenuProps: undefined,
});
}
private onColumnContextMenu = (column: IColumn, ev: React.MouseEvent<HTMLElement>): void => {
if (column.columnActionsMode !== ColumnActionsMode.disabled) {
this.setState({
contextualMenuProps: this.getContextualMenuProps(ev, column),
});
}
};
Finally, inside of getContextualMenuProps you need to determine what the options should be for the user to click. In this example, I'm simply giving sort options (you'll need to add an onClick handler to actually do something when the user clicks the item), but I'll use the column to determine what those items should actually be and paint the filters into the items collection so the user can select one to filter.
private getContextualMenuProps = (ev: React.MouseEvent<HTMLElement>, column: IColumn): IContextualMenuProps => {
const items: IContextualMenuItem[] = [
{
key: 'aToZ',
name: 'A to Z',
iconProps: { iconName: 'SortUp' },
canCheck: true,
checked: column.isSorted && !column.isSortedDescending,
},
{
key: 'zToA',
name: 'Z to A',
iconProps: { iconName: 'SortDown' },
canCheck: true,
checked: column.isSorted && column.isSortedDescending,
}
];
return {
items: items,
target: ev.currentTarget as HTMLElement,
directionalHint: DirectionalHint.bottomLeftEdge,
gapSpace: 10,
isBeakVisible: true,
onDismiss: this.onContextualMenuDismissed,
}
}
Note the target on the ContextualMenuProps object, which is what tells the ContextualMenu where to lock itself onto (in this case, the column header that you clicked to instantiate the menu.
Detail list filter for each column without context menu -
https://codesandbox.io/s/rajesh-patil74-jzuiy?file=/src/DetailsList.CustomColumns.Example.tsx
For instance - Providing filter in text field associated with each column will apply filter on color column.

Update rangeslider range on onClick event plotly.js

I have implemented a bar chart with rangeslider using react-plotly.js. I set initial range of the slider based on the input given in a date format. I have also implemented onClick event on barchart which would select a particular bar and change the color when I click on it.
In addition to that, I would also like to set the rangeslider to select the area where I have clicked the bar.
Currently, as you can see in the image above, the rangeslider sets itself back to the initial range value everytime the onClick event is performed. So in order to see the bar that was clicked, I have to manually move the slider to that area.
xaxis: {
type: 'date',
autorange: false,
fixedrange: true,
showgrid: false,
range: [moment.unix(this.state.startTime).format('YYYY-MM-DD'), moment.unix(this.state.endTime).format('YYYY-MM-DD')],
rangeslider: {
visible: true
}
}
<Plot
data={this.prepData(this.state.chartData)}
onClick={(data) => {
var colors = [];
var clickedPoint = data.points[0].pointNumber;
for (var i = 0; i < data.points[0].data.y.length; i++) {
if (i === clickedPoint) {
colors[i] = '#9b1e83';
} else {
colors[i] = this.state.defaultColor[i];
}
}
this.setState({
currentColorsArray: colors,
barClicked: data.points[0].pointNumber,
showSummaryPanel: true
})
}}
onHover={(hover) => this.getHoverInfo(hover)}
type={'bar'}
/>
How should I pass values to onRelayout such that it sets the rangeslider to the area where the onClick event was performed ?

ExtJS window animatetarget

I'm using animateTarget to animate the showing and hiding of a window. However i can't seem to set any animation options for this like duration and easing.
Only setting the duration and easing would be sufficient for me in this specific use case.
I'm using ExtJS 4.1
thnx!
It is possible to configure this animation but not with just additional configuration properties applied to a Ext.window.Window at creation. The animation is done with the animate method of the Ext.Element and is of type Ext.fx.Anim (see this link for config details)
You will need to extend from Ext.window.Window and override the afterShow() and onHide() private eventhandler. Within these you can modify the appropriate configs. Here's a example where I extended the duration from default 250ms to 500ms. And here's a working JSFiddle life-demo
Ext.define('Ext.ux.Window',{
extend: 'Ext.window.Window',
alias: 'widget.animatedwindow',
initComponent: function() {
this.callParent(arguments);
},
afterShow: function(animateTarget, cb, scope) {
var me = this,
fromBox,
toBox,
ghostPanel;
// Default to configured animate target if none passed
animateTarget = me.getAnimateTarget(animateTarget);
// Need to be able to ghost the Component
if (!me.ghost) {
animateTarget = null;
}
// If we're animating, kick of an animation of the ghost from the target to the *Element* current box
if (animateTarget) {
toBox = me.el.getBox();
fromBox = animateTarget.getBox();
me.el.addCls(me.offsetsCls);
ghostPanel = me.ghost();
ghostPanel.el.stopAnimation();
// Shunting it offscreen immediately, *before* the Animation class grabs it ensure no flicker.
ghostPanel.el.setX(-10000);
me.ghostBox = toBox;
ghostPanel.el.animate({
from: fromBox,
to: toBox,
duration: 500,
listeners: {
afteranimate: function() {
delete ghostPanel.componentLayout.lastComponentSize;
me.unghost();
delete me.ghostBox;
me.el.removeCls(me.offsetsCls);
me.onShowComplete(cb, scope);
}
}
});
}
else {
me.onShowComplete(cb, scope);
}
},
onHide: function(animateTarget, cb, scope) {
var me = this,
ghostPanel,
toBox,
activeEl = Ext.Element.getActiveElement();
// If hiding a Component which is focused, or contains focus: blur the focused el.
if (activeEl === me.el || me.el.contains(activeEl)) {
activeEl.blur();
}
// Default to configured animate target if none passed
animateTarget = me.getAnimateTarget(animateTarget);
// Need to be able to ghost the Component
if (!me.ghost) {
animateTarget = null;
}
// If we're animating, kick off an animation of the ghost down to the target
if (animateTarget) {
ghostPanel = me.ghost();
ghostPanel.el.stopAnimation();
toBox = animateTarget.getBox();
ghostPanel.el.animate({
to: toBox,
duration: 500,
listeners: {
afteranimate: function() {
delete ghostPanel.componentLayout.lastComponentSize;
ghostPanel.el.hide();
me.afterHide(cb, scope);
}
}
});
}
me.el.hide();
if (!animateTarget) {
me.afterHide(cb, scope);
}
}
});

Resources