Vis.js node tooltip doesn't show up on hover using ReactJS - javascript

Greetings,
In my project I am displaying a vis.js graph, using ReactJS and I would like to have the nodes of the displayed network react to the mouse-hover event by displaying a popup/tooltip. Every node has a title attribute, and thats what the tooltip should display.
This is how they do it in their example:
<!doctype html>
<html>
<head>
<title>Network | Interaction events</title>
<script type="text/javascript" src="../../../dist/vis.js"></script>
<link href="../../../dist/vis-network.min.css" rel="stylesheet" type="text/css" />
<style type="text/css">
#mynetwork {
width: 600px;
height: 400px;
border: 1px solid lightgray;
}
</style>
</head>
<body>
<div id="mynetwork"></div>
<script type="text/javascript">
// create an array with nodes
var nodes = new vis.DataSet([
{ id: 1, label: 'Node 1', title: 'I have a popup!' },
{ id: 2, label: 'Node 2', title: 'I have a popup!' },
{ id: 3, label: 'Node 3', title: 'I have a popup!' },
{ id: 4, label: 'Node 4', title: 'I have a popup!' },
{ id: 5, label: 'Node 5', title: 'I have a popup!' }
]);
// create an array with edges
var edges = new vis.DataSet([
{ from: 1, to: 3 },
{ from: 1, to: 2 },
{ from: 2, to: 4 },
{ from: 2, to: 5 }
]);
// create a network
var container = document.getElementById('mynetwork');
var data = {
nodes: nodes,
edges: edges
};
var options = {
interaction: { hover: true },
manipulation: {
enabled: true
}
};
var network = new vis.Network(container, data, options);
</script>
</body>
</html>
Here the popups work.
I am loading the node related data from a database, and the nodes are drawn according to that data just fine. Everything works properly, except the popup doesn't show when triggered by the hover event.
This is the relevant part of the component.tsx file:
import * as React from 'react';
import NodeGraph from 'react-graph-vis';
import { IEdge, ISkill } from '../../../models';
import Options from './skillTree.options';
import Props from './skillTree.props';
import State from './skillTree.state';
export default class extends React.Component<Props, State> {
constructor(props) {
super(props);
this.state = {
graph: { nodes: [], edges: [] },
options: Options,
selectedNode: undefined,
}
}
//...
private _skillTreeRequestCallback(graph: {
nodes: ISkill[],
edges: IEdge[]
}) {
graph.nodes = graph.nodes.map(skill => ({
...skill,
hidden: skill.Hidden,
image: skill.Image,
label: skill.Label,
id: skill.Id,
title: "Hardcoded popup message"
}));
graph.edges = graph.edges.map(edge => ({
...edge,
from: edge.From,
to: edge.To
}));
this.setState({ graph, isLoading: false });
}
//...
public render() {
return (this.state.graph.nodes.length > 0 ? <main style={this.props.style} >
<NodeGraph graph={this.state.graph} options={this.state.options}/>
</main> : <LoadingView style={this.props.style} />);
}
And the data arrives just fine from the database. The title attributes of the nodes are there, which I was able to log into the console after the query ran. That title attribute is what the vis.js canvas should display/draw as a popup when the hover event is triggered.
The relevant options that I load from this.state.options are:
interaction: {
hover: true
},
manipulation: {
enabled: true
},
Yet the popup message doesn't ever show up.
I tried to check whether the popup event actually happens, and seemingly it does, since the showPopup event of vis.js, -which happens when a popup is shown- does get triggered, the console.log I put into it runs, but the popup still doesn't actually show up on the screen.
In theory, in ReactJS I use the NodeGraph to draw the graph with the adequate data and it should do basically the same thing as this line:
var network = new vis.Network(container, data, options);
But apparently something with the popups gets lost in the ReactJS version, unless the reason is that I am doing something wrong.
Does anybody know of any solution that could help me display the title tooltip of a vis.js graph with a hover event in ReactJS?
Any help is much appreciated.
Thanks,
Balint

Cleanest way to do it,
in your index.css file add this line:
#import url("~vis/dist/vis.min.css");

Oddly by default the css is hiding the tooltip.
In your CSS set .vis-network: {overflow:visible;}

For me below CSS worked:
.vis-tooltip {
position: absolute;
}

I was having this issue as well a while back. It had to do with having the incorrect path to vis-network.min.css.
In the example they do this:
<link href="../../../dist/vis-network.min.css" rel="stylesheet" type="text/css" />
but in your own project that is probably not the correct path. Check your path and that should fix your problem.

Related

How to enforce first Node to be heading H1 in tiptap editor?

I am building a simple rich text editor for writing blogs. I want to enforce that first Node always be the title (h1 tag) which user should not be able to delete!
Is there a way to achieve this?
You can achieve this via Custom Document, where you can define the document structure. Tip tap has an example page here https://tiptap.dev/examples/custom-document.
You could put it in content when initializing:
const editor = useEditor({
extensions: [
StarterKit.configure({
heading: {
levels: [1, 2],
},
}),
Dropcursor.configure({
color: "#4B5563",
}),
Placeholder.configure({
showOnlyWhenEditable: true,
placeholder: "Write something or press Enter to add a new block",
}),
CodeBlockLowlight.configure({
lowlight,
}),
TaskList,
CustomTaskItem,
ListItem,
Blockquote,
CustomOrderedList,
CustomHorizontalRule,
Table.configure({
resizable: true,
}),
TableRow,
TableHeader,
TableCell,
],
editorProps: {
attributes: {
class: "focus:outline-none subpixel-antialiased",
},
},
autofocus: true,
// THIS
content: `
<h1>Hello there </h1>
`,
})
Extend Document:
const CustomDocument = Document.extend({
// https://tiptap.dev/api/schema#content
content: 'heading block*',
})
Load the CustomDocument and add default placeholder for Heading:
new Editor({
extensions: [
CustomDocument,
Placeholder.configure({
placeholder: ({ node }) => {
if (node.type.name === 'heading' && node.attrs.level == 1) {
return 'H1 placeholder here';
}
},
}),
]
})
After that, you have to find a solution for prevent bold/italic/H2/H3/etc. on your H1. If you have a solution for it, please give the solution in comment.

How to assign HTML element ID, through an Angular component, to a JavaScript function?

I found a JavaScript library and a function, I struggled, but did include this JavaScript function in my Angular application.
The JavaScript code prints takes in an object (myData: two arrays of numbers), and passes this data to a function, which draws two charts, each chart corresponds to one of the two arrays of numbers.
First thing I had to do is figure out how to include this JavaScript in my Angular project, which wasn't easy. And I'm even still not sure if it worked or not, because I haven't finished connecting everything together.
So here's the JavaScript code:
<!DOCTYPE html>
<html>
<head>
<!-- This page was last built at 2020-10-16 19:40 -->
<meta charset="utf-8"/>
<!-- Plotly.js -->
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
</head>
<body>
<div class="myDiv" id="myDiv">
</div>
<div class="myDiv" id="myDiv2">
</div>
<script>
myData = {
"loss": [
0.6437230565968681,
.
.
.
0.015016184188425541
],
"accuracy": [
0.6740923523902893,
.
.
.
0.9943200945854187
]
}
dataPlot(myData)
function dataPlot(myData){
function range(start, end) {
var ans = [];
for (let i = start; i <= end; i++) {
ans.push(i);
}
return ans;
}
yLoss = myData['loss'];
yAccuracy = myData['accuracy'];
traceLoss = {
type: 'scatter',
x: range(yLoss.length>0?1:0,yLoss.length),
y: yLoss,
mode: 'lines',
name: 'Red',
line: {
color: 'rgb(219, 64, 82)',
width: 1
}
};
traceAccuracy = {
type: 'scatter',
x: range(yAccuracy.length>0?1:0,yAccuracy.length),
y: yAccuracy,
mode: 'lines',
name: 'Blue',
line: {
color: 'rgb(55, 128, 191)',
width: 1
}
};
var layoutLoss = {
title: 'Model Binary Crossentropy Loss',
xaxis: {
title: 'Epochs',
showgrid: false,
zeroline: false
},
yaxis: {
title: 'Loss',
showline: false
}
// for static size
,width: 900,
height: 500
};
var layoutAccuracy = {
title: 'Model Training Data Accuracy',
xaxis: {
title: 'Epochs',
showgrid: false,
zeroline: false
},
yaxis: {
title: 'Accuracy',
showline: false
}
// for static size
,width: 900,
height: 500
};
var dataLoss = [traceLoss];
var dataAccuracy = [traceAccuracy];
divs('myDiv','myDiv2')
function divs(div1, div2) {
Plotly.newPlot(div1, dataLoss, layoutLoss);
Plotly.newPlot(div2, dataAccuracy, layoutAccuracy);
}
}
</script>
</body>
</html>
If you copy that code to a .html file it'll work.
See at the bottom of the code this function:
function divs(div1, div2) {
Plotly.newPlot(div1, dataLoss, layoutLoss);
Plotly.newPlot(div2, dataAccuracy, layoutAccuracy);
}
This function takes div1 and div2 values, those are the IDs for the 2 divs containing the charts.
What I need is, first, to pass myData to the function, which is easy and doable, not a problem.
The problem is, how am I supposed to give the IDs of the divs to the JavaScript function, and have it know where those divs are to begin with?
I've been trying with #ViewChildren and ElementRef. But no success.
Here's my HTML template so far:
<div #divs *ngFor="let item of [1,2]" [id]="'myDiv-' + item">Div {{item}}+{{whatevs}}</div>
<button (click)="getDivs()">Get divs</button>
and the component:
import { Component, ElementRef, OnInit, QueryList, ViewChildren } from '#angular/core';
declare var dataPlot: any;
#Component({
selector: 'jhi-ml-model-data',
templateUrl: './ml-model-data.component.html',
})
export class MlModelDataComponent implements OnInit {
#ViewChildren("divs", { read: ElementRef }) divs!: QueryList<ElementRef>;
myData: any;
constructor() { }
getDivs(): void {
// this.divs.forEach((div: ElementRef) => div.nativeElement);
this.divs.forEach((div: ElementRef) => console.log(div.nativeElement.getAttribute('id')));
// this^ gets the IDs for both divs, not each one individually.
}
ngOnInit(): void {
this.myData = {
"loss": [0.6437230565968681,
.
.
.
0.39040950554258685],
"accuracy": [0.6740923523902893,
.
.
.
0.9943200945854187]
}
dataPlot(this.myData);
dataPlot.divs();
}
}
I feel like I've put myself in an untangleable mess, and feel like there must be

Vue range slider making page not scrollable on mobile

In VUE I have a range slider component that I use to display different values at set point when the user drags the slider.
This is all working fine, the only problem that I have is that the slider VUE slider component is making my page not scrollable on mobile.
Is the browser getting confused somehow with the drag action, meaning it doesn't know it's happening on the slider but on the actual page?
Any ideas how I can solve this? Thanks
<div class='slider margin-top-10 margin-bottom-40'>
<range-slider
v-model="value"
:min="min"
:max="max"
:step="step"
:range="range"
:height="barheight"
:dot-height="dotheight"
:dot-width="dotwidth"
:piecewise-label="label"
:process-style="processstyle">
</range-slider>
</div>
import RangeSlider from 'vue-range-component'
export default {
components: {
RangeSlider
},
props: {
membership: {
type: Object,
},
translations: {
type: Object
},
isAgency: {
type: Boolean
},
clientsCap: {
type: Number
}
},
data: function() {
return {
value: 10,
min: 10,
max: 50,
step: 10,
data: [10, 20, 30, 40, 50,],
range: [{label: '10'}, {label: '20'}, {label: '30'}, {label: '40'}, {label: '50'}],
label: true,
barheight: 3,
dotwidth: 16,
dotheight: 16,
processstyle: { backgroundColor: 'transparent'}
}
},
created: function(){
this.$emit('updateImages', this.value);
},
watch: {
value: function(){
this.$emit('updateImages', this.value);
}
},
computed: {
price: function() {
var price = this.value * this.membership.additional_images;
if(this.isAgency)
price = price * this.clientsCap;
if(this.membership.priceOffered < this.membership.basePrice && this.membership.priceOffered !== undefined)
price = price - (price * 0.10);
return price;
}
}
}
This bug already fixed in github repo BUT npm repo not updated.
https://github.com/xwpongithub/vue-range-slider/blob/master/src/js/vue-range-slider.js#L1006
So you need remove installed package from npm
"vue-range-component": "^1.0.3",
and add directly from github
"vue-range-component": "xwpongithub/vue-range-slider",
A solution to this problem may be the following.
In the created function of Vue where you have this component place the following lines.
created () {
VueRangeSlider.methods.handleKeyup = ()=> console.log;
VueRangeSlider.methods.handleKeydown = ()=> console.log;
},
I also ran into the problem of entering text input fields and the fact that the scroll is blocked on mobile devices.
To solve it, I downloaded the library itself and deleted all the event processing methods and keys (keydown, keyup), this helped with a text input problem. For scrolling, you need to delete the line f = "touchstart"
You need to clear the line: x="" and w="" It's, rough, line 73 and 74

Adding a tooltip in a Dojo Select

I would like to add a tooltip to the items in a Dojo Select. This code adds a tooltip when the store is contained in the script.
<!DOCTYPE html>
<html>
<head>
<style type="text/css">
#import "https://ajax.googleapis.com/ajax/libs/dojo/1.9.1/dijit/themes/claro/claro.css";
#import "https://ajax.googleapis.com/ajax/libs/dojo/1.9.1/dojo/resources/dojo.css";
</style>
<script src="https://ajax.googleapis.com/ajax/libs/dojo/1.9.0/dojo/dojo.js" type="text/javascript" data-dojo-config="async: true"></script>
<script>
require(["dijit/form/Select",
"dojo/store/Memory",
"dojo/domReady!"
], function (Select, Memory) {
var store = new Memory({
data: [
{ id: "foo", label: '<div tooltip="Foo Tooltip" onmouseover="showTooltip(this)" onmouseout="hideTooltip(this)">FOO</div>' },
{ id: "bar", label: '<div tooltip="Bar Tooltip" onmouseover="showTooltip(this)" onmouseout="hideTooltip(this)">Bar</div>' }
]
});
var s = new Select({
store: store,
labelType: 'html',
labelAttr: 'label'
}, "target");
s.startup();
});
function showTooltip(el) {
dijit.showTooltip(el.getAttribute('tooltip'), el);
}
function hideTooltip(el) {
dijit.hideTooltip(el);
}
</script>
</head>
<body class="claro">
<div id="target"></div>
</body>
</html>
However, in my application, my store is in a separate module (stores.js).
define([], function () {
return {
priority: [
{ id: "foo", label: '<div tooltip="Foo Tooltip" onmouseover="showTooltip(this)" onmouseout="hideTooltip(this)">FOO</div>' },
{ id: "bar", label: '<div tooltip="Bar Tooltip" onmouseover="showTooltip(this)" onmouseout="hideTooltip(this)">Bar</div>' }
]
};
};
I set the module in the require ("modules/stores") and put the alias in the function (Stores) and create my select using this code.
new Select({
id: "cboPriority",
store: new Memory({ data: Stores.priority }),
labelType: 'html',
labelAttr: 'label'
}, "divPriority").startup();
I've tried adding the showTooltip and hideTooltip functions in the module, but I still get the console error "ReferenceError: showTooltip is not defined". What is the proper way of setting up the script and the module so I can show the tooltip?
You're attempting to set up inline onmouseover event handlers on elements via your label strings. This is going to attempt to call a global showTooltip function, and no such function exists - your showTooltip function is enclosed within your require factory function.
Given that you are creating an HTML label with a node containing an attribute indicating the text to display, a better option in this specific case would be to use dojo/on's event delegation to hook up a single event handler for mouseover and another for mouseout:
var dropdownNode = s.dropDown.domNode;
on(dropdownNode, '[data-tooltip]:mouseover', function () {
Tooltip.show(this.getAttribute('data-tooltip'), this);
});
on(dropdownNode, '[data-tooltip]:mouseout', function () {
Tooltip.hide(this);
});
(Tooltip in the above code refers to the dijit/Tooltip module, and I elected to use a data-attribute which would at least be valid HTML5.)
To be quite honest, I'd prefer avoiding embedding HTML in data to begin with, but this is likely the shortest path from where you are to where you want to be.

Adding data to highcharts(Highstock) when user scrolling reaches left end

I'm using highstock on my website. The scroll bar in the navigator of stock chart is drawn using SVG. I want to add more data(via ajax) to the graph when user scrolls to the leftmost end.
I am new to SVG and not sure how to detect that user has scrolled to the end and fire a ajax query based on that. Can anyone help me with this.
Thanks,
Sivakumar.
So, today I had the same problem, and I just found your question.
I don't know if you have any reason for loading the new data only when the user moves the scrollbar, I would recommend to fire the ajax query if the user visualizes the left-most data, instead (that is: scrolling the bar, pressing the left arrow, dragging the area of the navigation chart, and so on).
If this solution applies to you, you can try with something like this:
chart = new Highcharts.StockChart({
chart: {
renderTo: 'chart',
events: {
redraw: function(event) {
if (chart.xAxis) {
var extremes = chart.xAxis[0].getExtremes();
if (extremes && extremes.min == extremes.dataMin) {
console.log("time to load more data!");
}
}
}
}
}, [...]
I solved this problem in the following way.
<script>
import axios from 'axios';
export default {
name: 'Chart',
data() {
return {
chartOptions: {
chart: {
events: {
// we will lose the context of the component if we define a function here
}
},
series: [{
type: 'candlestick',
data: null,
}],
},
};
},
methods: {
onRedraw: function () {
let chart = this.$refs.chart.chart
if (chart.xAxis) {
let extremes = chart.xAxis[0].getExtremes()
console.log(extremes)
if (extremes && extremes.min <= extremes.dataMin) {
console.log("time to load more data!");
}
}
}
},
created() {
axios.get('https://demo-live-data.highcharts.com/aapl-ohlcv.json').then((response) => {
this.chartOptions.series[0].data = response.data
this.chartOptions.series[0].name = 'AAPL'
});
this.chartOptions.chart.events.redraw = this.onRedraw
},
};
</script>
<template>
<highcharts :constructor-type="'stockChart'" :options="chartOptions" ref="chart"></highcharts>
</template>

Categories

Resources