Custom hovers using plotly.js with react - reactjs

Looking for the best way of implementing a custom hover with plotly.js along with react. The following is in the plotly.js docs https://plot.ly/javascript/hover-events/
var myPlot = document.getElementById('myDiv'),
hoverInfo = document.getElementById('hoverinfo'),
d3 = Plotly.d3,
N = 16,
x = d3.range(N),
y1 = d3.range(N).map( d3.random.normal() ),
y2 = d3.range(N).map( d3.random.normal() ),
data = [ { x:x, y:y1, type:'scatter', name:'Trial 1',
mode:'markers', marker:{size:16} },
{ x:x, y:y2, type:'scatter', name:'Trial 2',
mode:'markers', marker:{size:16} } ];
layout = {
hovermode:'closest',
title:'Hover on Points'
};
Plotly.plot('myDiv', data, layout);
myPlot.on('plotly_hover', function(data){
var infotext = data.points.map(function(d){
return (d.data.name+': x= '+d.x+', y= '+d.y.toPrecision(3));
});
hoverInfo.innerHTML = infotext.join('
');
})
.on('plotly_unhover', function(data){
hoverInfo.innerHTML = '';
});
But when using react I'm not sure how to get a reference to the #hoverInfo div. Any suggestions?

You can use ref
render() {
<div id='hoverInfo' ref={ (el) => this.hoverInfo = el }>
</div>
}
will give you a reference in your component to the hoverInfo div which can be used with a library like plotly.

I am facing the same problem and found the easiest way to customize hovers is using the hovertempleate property, if you only want to customized the text: https://plotly.com/javascript/hover-text-and-formatting/
Another option is playing with onHover and onUnhover props in the layout: https://github.com/plotly/react-plotly.js/ You can define an state hover which turns true when hovered and false when unhovered. With hover true you could make your component to appear with the information you need.

Related

How to redraw text rendered using SVGRenderer in Highcharts React?

I am using the SVGRenderer to draw the total value in the center of donut chart shown below:
The code to do this is shown below (please see CodeSandbox here)
export const PieChart = ({ title, totalLabel, pieSize, pieInnerSize, data }: PieChartProps) => {
const chartRef = useRef(null);
const [chartOptions, setChartOptions] = useState(initialOptions);
useEffect(() => {
// set options from props
setChartOptions({...});
// compute total
const total = data.reduce((accumulator, currentValue) => accumulator + currentValue.y, 0);
// render total
const totalElement = chart.renderer.text(total, 0, 0).add();
const totalElementBox = totalElement.getBBox();
// Place total
totalElement.translate(
chart.plotLeft + (chart.plotWidth - totalElementBox.width) / 2,
chart.plotTop + chart.plotHeight / 2
);
...
}, [title, totalLabel, pieSize, pieInnerSize, data]);
return (
<HighchartsReact
highcharts={Highcharts}
containerProps={{ style: { width: '100%', height: '100%' } }}
options={chartOptions}
ref={chartRef}
/>
);
};
However this approach has two issues:
When chart is resized, the total stays where it is - so it is no longer centered inside the pie.
When the chart data is changed (using the form), the new total is drawn over the existing one.
How do I solve these issues? With React, I expect the chart to be fully re-rendered when it is resized or when the props are changed. However, with Highcharts React, the chart seems to keep internal state which is not overwritten with new props.
I can suggest two options for this case:
Use the events.render callback, destroy and render a new label after each redraw:
Demo: https://jsfiddle.net/BlackLabel/ps97bxkg/
Use the events.render callback to trasnlate those labels after each redraw:
Demo: https://jsfiddle.net/BlackLabel/6kwag80z/
Render callback triggers after each chart redraw, so it is fully useful in this case - more information
API: https://api.highcharts.com/highcharts/chart.events.render
I'm not sure if this helps, but I placed items inside of highcharts using their svgrenderer functionality (labels, in particular, but you can also use the text version of svgrenderer), and was able to make them responsive
I was able to do something by manually changing the x value of my svgRenderer label.
I'm in angular and have a listener for screen resizing:
Also Note that you can change the entire SVGRenderer label with the attr.text property.
this.chart = Highcharts.chart(.....);
// I used a helper method to create the label
this.chart.myLabel = this.labelCreationHelperMethod(this.chart, data);
this.windowEventService.resize$.subscribe(dimensions => {
if(dimensions.x < 500) { //this would be your charts resize breakpoint
// here I was using a specific chart series property to tell where to put my x coordinate,
// you can traverse through the chart object to find a similar number,
// or just use a hardcoded number
this.chart.myLabel.attr({ y: 15, x: this.chart.series[0].points[0].plotX + 20 });
} else {
this.chart.myLabel.attr({ y: 100, x: this.chart.series[0].points[0].plotX + 50 });
}
}
//Returns the label object that we keep a reference to in the chart object.
labelCreationHelperMethod() {
const y = screen.width > 500 ? 100 : 15; 
const x = screen.width > 500 ? this.chart.series[0].points[0].plotX + 50 :
this.chart.series[0].points[0].plotX + 20
// your label
const label = `<div style="color: blue"...> My Label Stuff</div>`
return chart.renderer.label(label, x, y, 'callout', offset + chart.plotLeft, chart.plotTop + 80, true)
.attr({
fill: '#e8e8e8',
padding: 15,
r: 5,
zIndex: 6
})
.add();
}

How to toggle off Openlayers custom control?

I've gone ahead and built a custom control that I'm adding to my map like so:
const BoundingBox = (function (Control) {
function BoundingBox(optOptions) {
const options = optOptions || {};
const button = document.createElement('button');
button.innerHTML = '[]';
const element = document.createElement('div');
element.className = 'bounding-box ol-unselectable ol-control';
element.appendChild(button);
Control.call(this, {
element,
target: options.target,
});
button.addEventListener('click', this.handleBoundingBox.bind(this), false);
}
if (Control) BoundingBox.__proto__ = Control;
BoundingBox.prototype = Object.create(Control && Control.prototype);
BoundingBox.prototype.constructor = BoundingBox;
BoundingBox.prototype.handleBoundingBox = function handleBoundingBox() {
this.getMap().addInteraction(extent);
};
return BoundingBox;
}(Control));
Next, I added that control to my map when my map is initialized. This is working fine. Now, I'm trying to find a way to toggle off the BoundingBox control. I was thinking that I could use the .removeInteraction() method. However, I'm unsure if that's correct. Also, should that be applied in a separate function or in my BoundingBox control?
I was able to accomplish this by checking the properties of the ol/interaction/extent and setting the value of the active property to false.
extent.setProperties({ active: false });

React Kendo Treeview scroll to item

I am using React Kendo Treeview UI. I want to try to scroll to the item that is selected in the tree. I found many examples for Javascript and JQuery but none for React version. I couldn't solve this problem by playing around with it.
Items in the tree are of type MyViewTreeModel. I have a selectOntree method that finds a node and set the selected to true. My problem is I want to scroll to that item.
export interface MyViewTreeModel {
text: string,
expanded: boolean,
employeeId : number,
treeId: number,
items?: MyViewTreeModel [],
selected: boolean
}
....
<TreeView
data={myData}
expandIcons={true}
onExpandChange={onExpandChange}
onItemClick={OnItemClick}
aria-multiselectable={false}
aria-label={'text'}
></TreeView>
....
const selectOnTree = (employeeId: number ) => {
let treeItem = recursivelyFindEmployeeInTree(myData[0], employeeId);
treeItem.selected = true;
forceUpdate();
}
}
myData is of type MyViewTreeModel .
One solution I tried : I added ref?: any to my model and tried treeItem.ref.current.focus(); in selectOnTree function, but ref was undefined.
Another solution I tried is adding this property to TreeView:
ref={component => treeViewRef.current = component}
Then tried this just to select the first 'li' tag in the TreeView:
if(!_.isNil(treeViewRef.current) ){
let domElement = ReactDOM.findDOMNode(treeViewRef.current);
let treeItemDom = domElement.firstChild.firstChild;
(treeItemDom as HTMLElement).focus();
}
This didn't work, it doesn't put the focus at that point.
I am thinking maybe I should define a custom itemRender that has a ref that I can find the offsetTop of it, but then there are more than one item, how can I create a different ref for each one? Or maybe a custom ItemRender that renders an input (with css I can make it look like a span) and then set autofocus to true if selected is true. Not sure if autofocus true make it scroll to that item.
This is the solution I could find to make it work:
Adding a reference to TreeView
let treeViewRef = useRef(null);
In return statement:
<TreeView
data={myData}
expandIcons={true}
onExpandChange={onExpandChange}
onItemClick={OnItemClick}
aria-multiselectable={false}
aria-label={'text'}
ref={component => treeViewRef.current = component}></TreeView>
2.Defined this function to scroll to a specific treeItem:
'k-in' is the className for each span that represent each item in the Kendo Treeview UI component.
const scrollToTreeViewItem = (treeItem: MyViewTreeModel ) => {
if(!_.isNil(treeViewRef.current)){
let domElement = ReactDOM.findDOMNode(treeViewRef.current);
let treeItemDoms = (domElement as Element).querySelectorAll('.k-in');
let domArray = [];
treeItemDoms.forEach((node) => {
domArray.push(node as HTMLElement);
});
let targetedDomElement = domArray.find((item) => {
return item.innerText === treeItem.text;
});
targetedDomElement.scrollIntoView();
}
}

Rendering a portion of code (tablesorter with selectize) everytime I load a React component

My app is rendering a portion of code everytime I load a component. This is the code:
https://jsfiddle.net/rLvfa8rn/
I'm trying to implement this http://jsfiddle.net/Unspecified/qrqJv/1/ on my tablesorter table.
The problem is with the portion of lines 71-121, there's a dropdown of Selectize.js rendering everytime I call the page:
Selectize.define( 'clear_selection', function ( options ) {
var self = this;
var title = options.title || 'Sin filtro';
//Overriding because, ideally you wouldn't use header & clear_selection simultaneously
self.plugins.settings.dropdown_header = {
title: title
};
this.require( 'dropdown_header' );
self.setup = (function () {
var original = self.setup;
return function () {
original.apply( this, arguments );
this.$dropdown.on( 'mousedown', '.selectize-dropdown-header', function ( e ) {
self.setValue( '' );
self.close();
self.blur();
return false;
});
}
})()
});
I put all the code because maybe the problem is another.
Well, all the problem was in the var selectize({
var selectize({
hideSelected: false,
dropdownParent: 'body'
the option: dropdownParent: 'body' was the problem, it's a know bug of selectize I guess. Removing that option works fine.

angular leaflet marker change property with function

I would like to change some leaflet marker properties by clicking a link outside the map, but it doesn't work.
Check out this fiddle:
http://jsfiddle.net/Ls59qLLa/2/
js:
var app = angular.module('demoapp',['leaflet-directive']);
app.controller('DemoController', [ '$scope', 'leafletData', function($scope, leafletData) {
var local_icons = {
defaultIcon: {},
gmapicon: {
iconUrl: 'http://maps.google.com/mapfiles/kml/shapes/capital_big.png',
iconSize: [25, 25],
iconAnchor: [12, 12],
popupAnchor: [0, 0]
}
}
angular.extend($scope, {
markers: {
m1: {
lat: 41.85,
lng: -87.65,
clickable: false,
message: "I'm a static marker",
icon: local_icons.gmapicon
}
}
});
$scope.makeIconClickable = function(){
alert('function called');
var whichmarker = 'm1';
$scope.markers[whichmarker].clickable = true;
}
}]);
HTML:
<body ng-controller="DemoController">
<leaflet markers="markers"></leaflet>
<a href="#" ng-click=makeIconClickable()>Make Icon Clickable</a>
</body>
Leaflet directive has separate $watch for every marker (and path) on the map. When you change one of the marker property, this $watch is fired and it checks if some of properties has changed. Apparently it does not look for the clickable property, but it looks for message property. So in your function you could set message of the marker and everything binds properly:
$scope.makeIconClickable = function(){
var whichmarker = 'm1';
$scope.markers[whichmarker].clickable = true;
$scope.markers[whichmarker].message = "I'm a static marker";
}
If you need to set message on marker initialization phase, you can use some temporary property:
angular.extend($scope, {
markers: {
m1: {
lat: 41.85,
lng: -87.65,
icon: local_icons.gmapicon,
tmpmessage: 'I am a static marker'
}
}
});
And then:
$scope.makeIconClickable = function(){
var whichmarker = 'm1';
$scope.markers[whichmarker].clickable = true;
$scope.markers[whichmarker].message = $scope.markers[whichmarker].tmpmessage;
}
Working example: http://jsfiddle.net/3zgL8m4u/
I had the same problem. I sometimes need the map to fire an onClick Event even if the user clicked the Marker. The answer provided by "Agnieszka Świecznik" is a quick and easy workaround if all you need is no Popup to be shown. It does not solve my problem, since it only removes the Popup, but no onClick Event on the map is fired.
To solve this i found this official Thread which seems to have been opened by the OP: https://github.com/tombatossals/angular-leaflet-directive/issues/676
Here a link to a modified JSFiddle is Provided: http://jsfiddle.net/nmccready/gbd1aydL/
The user who provided this fix included the following codeblock in the "angular-leaflet-directive.js" which can be found in the jsfiddle under "external resources":
var _destroy = function(markerModels, oldMarkerModels, lMarkers, map, layers){
// Delete markers from the array
var hasLogged = false,
modelIsDiff = false;
var doCheckOldModel = isDefined(oldMarkerModels);
for (var name in lMarkers) {
if(!hasLogged) {
$log.debug(errorHeader + "[markers] destroy: ");
hasLogged = true;
}
if(doCheckOldModel){
//might want to make the option (in watch options) to disable deep checking
//ie the options to only check !== (reference check) instead of angular.equals (slow)
modelIsDiff = !angular.equals(markerModels[name],oldMarkerModels[name]);
}
if (!isDefined(markerModels) ||
!Object.keys(markerModels).length ||
!isDefined(markerModels[name]) ||
!Object.keys(markerModels[name]).length ||
modelIsDiff) {
deleteMarker(lMarkers[name], map, layers);
delete lMarkers[name];
}
}
};
Note that this seems to be an older Version, as noted in the file itself: "angular-leaflet-directive 0.7.11 2015-04-08". When included in my AngularJS Project this allowed me to change the clickable property of the marker by just modifiyng the corresponding boolean in my "$scope.markers"-equivalent.

Resources