BokehJS Custom Tool for Toggling Legend Visibility - javascript

My bokeh app is dealing with a grid of several figures, each showing several glyphs. To improve readability, I'd like to be able to hide/show the legends of the figures on clicking on a button. Though this appeared to me as a perfect example for a tool button in the toolbar such as 'save' and 'reset', this functionality is not implemented in bokeh yet.
I found several hints on how to implement a custom tool myself, see here, here, here, here or here. An example on how to add a custom icon is shown here.
This is what I got so far:
main.py:
from bokeh.layouts import gridplot
from bokeh.models import ColumnDataSource, Tool
from bokeh.plotting import figure
from os.path import dirname
from jinja2 import FileSystemLoader, Environment
from bokeh.embed.standalone import file_html
from bokeh.resources import CDN
class LegendToggleTool(Tool):
__implementation__ = """
import {ActionTool, ActionToolView} from "models/tools/actions/action_tool"
import * as p from "core/properties"
export class LegendToggleToolView extends ActionToolView {
model: LegendToggleTool
doit(): void {
for(const r of this.plot_view.model.panels){
if (r.type=="Legend"){
r.visible = !r.visible
}
}
}
}
export namespace LegendToggleTool {
export type Attrs = p.AttrsOf<Props>
export type Props = ActionTool.Props
}
export interface LegendToggleTool extends LegendToggleTool.Attrs {}
export class LegendToggleTool extends ActionTool {
properties: LegendToggleTool.Props
__view_type__: LegendToggleToolView
constructor(attrs?: Partial<LegendToggleTool.Attrs>) {
super(attrs)
}
static init_LegendToggleTool(): void {
this.prototype.default_view = LegendToggleToolView
this.register_alias("legendtoggle", () => new LegendToggleTool())
}
tool_name = "LegendToggle"
icon = "legend-toggle-icon"
}
"""
env = Environment(loader=FileSystemLoader(dirname(__file__)))
template = env.get_template('template.html')
source01 = ColumnDataSource(data=dict(x=[0,1,2,3,4], y=[0,1,2,3,4],z=[4,3,2,1,0]))
source02 = ColumnDataSource(data=dict(x=[0,3,1,6,1], y=[0,1,2,3,4],z=[4,3,2,1,0]))
fig01 = figure(x_range=(0, 10), y_range=(0, 10),tools=[LegendToggleTool()])
fig01.line('x', 'y', source=source01, legend_label = 'line_01')
fig01.line('x', 'z', source=source01, legend_label = 'line_02')
fig02 = figure(x_range=(0, 10), y_range=(0, 10),tools=[LegendToggleTool()])
fig02.line('x', 'y', source=source02, legend_label = 'line_03')
fig02.line('x', 'z', source=source02, legend_label = 'line_04')
with open('out.html', 'w') as f:
f.write(file_html(gridplot([[fig01,fig02]]), resources=CDN, template=template))
template.html (in the same directory, copied from here):
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>{{ title|e if title else "Bokeh Plot" }}</title>
{{ bokeh_css }}
{{ bokeh_js }}
<style>
html {
width: 100%;
height: 100%;
}
body {
width: 90%;
height: 100%;
margin: auto;
}
.legend-toggle-icon {
background-image: url(data:image/png;base64,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);
}
</style>
</head>
<body>
{{ plot_div|indent(8) }}
{{ plot_script|indent(8) }}
</body>
</html>
Running main.py results in an output-file 'out.html'. Toggling the legends' visibility works as expected, but the icon remains blank. So here are my questions:
How do I get the icon to show up?
Let's say I wanted to run this via bokeh serve --show <dirname>, how would I need to organize the above code inside the folder <dirname>?

Related

Bpmn io vue 3 integration problems

Im currently trying to embed bpmn io in a vue 3 application. Im able to load the diagram using the raw loader in webpack. Unfortunately there are some other issues.
1. The side bar on the left is not appearing
2. The canvas.zoom is not working. Diagram occupies only a small portion of the screen width and height.
MainPage.vue (file where bpmn magic resides)
<template>
<div ref="container" id="canvas" style="height: 100%"/>
</template>
<script>
import pizzaDiagram from '../assets/pizza-diagram.bpmn';
export default {
name: 'main-page',
mounted() {
this.$nextTick(() => {
const container = this.$refs.container;
let modeler = this.$bpmnModeler;
modeler.attachTo(container)
modeler.options = {
container,
height: "100%",
width: "100%"
}
modeler.importXML(pizzaDiagram).then((result) => {
const {warnings} = result;
console.log('success !', warnings);
const canvas = modeler.get('canvas');
canvas.zoom('fit-viewport')
}).catch((err) => {
const {warnings, message} = err;
console.trace('something went wrong. what went wrong :', warnings, message)
})
})
},
data() {
return {}
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
#canvas{
height: 100%;
width: 100%;
}
</style>
main.js (file where i register my bpmn components for general use app-wide)
import { createApp } from 'vue'
import App from './App.vue'
import BpmnJS from 'bpmn-js/dist/bpmn-navigated-viewer.production.min.js'
import BpmnModeler from "bpmn-js";
import BpmnViewer from "bpmn-js";
const app = createApp(App);
app.config.globalProperties.$bpmnViewer = new BpmnViewer();
app.config.globalProperties.$bpmnModeler = new BpmnModeler();
app.config.globalProperties.$bpmnInstance = new BpmnJS();
app.mount('#app')
I was able to solve this. The problem was that i was using the ref=container on the template and then passing it to the canvas options. When i used `document.getElementById("container") to refer to the element i was able to get it to work.

How can I embed VaniilaJS into React?

I have open source library that I want to use. the library wrote in clean vanilla js:
follow their docs, if I want to use the library:
<html>
<head>
<script src="./jquery-2.0.3.min.js"></script>
<script src="./kinetic-v5.1.0.min.js"></script>
<script src="./inchlib-1.2.0.js"></script>
<script>
$(document).ready(function() { //run when the whole page is loaded
var inchlib = new InCHlib({"target": "inchlib",
"width": 800,
"height": 1200,
"column_metadata_colors": "RdLrBu",
"heatmap_colors": "RdBkGr",
"max_percentile": 90,
"middle_percentile": 60,
"min_percentile": 10,
"heatmap_font_color": "white",
text: 'biojs'});
inchlib.read_data_from_file("/microarrays.json");
inchlib.draw();
inchlib.onAll(function(name){
console.log(name + " event triggered");
});
});
</script>
</head>
<body>
<div class="heatmaps" style="margin:auto; align-items: center; margin-left:25%;">
<div id="inchlib"></div>
</div>
<div ></div>
</body>
</html>
The file inchlib-1.2.0.js contains the main logic and js code. I want to build react project and use this library there. How can I achieve this goal?
import React, { Component } from 'react';
import './App.css';
export default class App extends Component {
render () {
return (
<div>
<div>
</div>
</div>
)
}
}
You can create custom hook with useEffect. In useEffect you should paste your code. You can insert html elements, add event listeners and so on.
useLibrary.js
import { useEffect } from "react";
const useLibrary = () => {
useEffect(() => {
$.getScript("inchlib-1.2.0.js", function(){
var inchlib = new InCHlib({"target": "inchlib",
"width": 800,
"height": 1200,
"column_metadata_colors": "RdLrBu",
"heatmap_colors": "RdBkGr",
"max_percentile": 90,
"middle_percentile": 60,
"min_percentile": 10,
"heatmap_font_color": "white",
text: 'biojs'});
inchlib.read_data_from_file("/microarrays.json");
inchlib.draw();
inchlib.onAll(function(name){
console.log(name + " event triggered");
});
});
}, []);
};
export default useLibrary;
App.js
import useLibrary from ".useLibrary";
export default class App extends Component {
useLibrary();
render () {
return (
<div>
<div class="heatmaps" style="margin:auto; align-items: center; margin-left:25%;">
<div id="inchlib"></div>
</div>
</div>
)
}
}
But I warn you that this is a big crutch.
Depends on what you're gonna do with the library you want to integrate with. Checkout this as a base reference: Integrating with other libraries.
If you're gonna manipulate DOM elements you'll gonna need a reference to them. In this case checkout this: Refs and the DOM.
If the library provides some general logic, you have no problem using it anywhere throughout your code or more specifically in effects.
As inchlib is a visual element library, you'll need to go the first route and get a reference to a specific DOM element. As already noted, checkout Refs from react docs.
Alternative solution is to wrap the whole library usage in your own react component.
Well If I were to do the same thing then I would paste the script tags as you've done in your html file
<head>
<script src="./jquery-2.0.3.min.js"></script>
<script src="./kinetic-v5.1.0.min.js"></script>
<script src="./inchlib-1.2.0.js"></script>
<script>
</head>
For accessing an object into react app, Create a file named Inchlib.js in same directory as is your app.js
Contents of Inchlib.js should be
export default window.InCHlib;
Import the default export into your app.js
import InCHlib from "./inchlib";
function App() {
console.log(InCHlib); // prints the InCHlib object
return "hello";
}
Note: Although this should work, there might be a better way to do this. Also using global objects in react code is not usually a preferred option.
Hopefully this would help.
Just add the Libraries and Scripts you want in the public/index.html file in your react project.
create loadScript function:
function loadScript(src, position, id) {
if (!position) {
return;
}
const script = document.createElement('script');
script.setAttribute('async', '');
script.setAttribute('id', id);
script.src = src;
position.appendChild(script);
}
in Component:
export default function GoogleMaps() {
const loaded = React.useRef(false);
if (typeof window !== 'undefined' && !loaded.current) {
if (!document.querySelector('#google-maps')) {
loadScript(
'https://maps.googleapis.com/maps/api/js?key=AIzaSyBwRp1e12ec1vOTtGiA4fcCt2sCUS78UYc&libraries=places',
document.querySelector('head'),
'google-maps',
);
}
loaded.current = true;
}
}
now you can access window.google
here is a example

jQuery plugin not accessible in React

I have not been successful in importing the following jQuery plugin into my React app:
http://bililite.com/inc/jquery.parsecss.js
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
<script src="http://bililite.com/inc/jquery.parsecss.js"></script>
</head>
<body>
<div id="root"></div>
</body>
</html>
My React component:
import React, { Component } from "react";
class Main extends Component {
constructor(props) {
super(props);
$.parsecss(".mybutton { color: #ff0000; }", function(css) {
var x = 0;
x++;
});
}
}
The jQuery function parsecss is undefined.
Adding it to my index.html using a script tag did not work. Only when adding it as a node module and importing it, did it work. Of course, many jquery plugins are not available using npm, so I had to create my own package. This was actually quite simple. In my node_modules folder I created a folder called parsecss and a sub folder called dist. In dist I placed the jquery plugin. In the parsecss folder I added a package.json file that had the following content:
{
"main": "dist/jquery.parsecss.js",
"name": "parsecss",
"title": "parsecss"
}
You need to add jQuery as I showed in my sample code above in the index.html file:
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
And in the React component:
/* global $ */
import React, { Component } from "react";
import parsecss from 'parsecss'
class Main extends Component {
constructor() {
$.parsecss(".mybutton { color: #ff0000; }", function(css) {
var x = 0;
x++;
});
}
}
Note: The comment /* global $ */ fixes a console error that indicates that $ is not defined.

Can not get media templates working using styled-components

I am fairly new to styled-components, and I am trying to get media templates working in my react app. It was created using 'create-react-app'
I followed the code posted in styled-components documentation:
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
import styled from 'styled-components';
const sizes = {
desktop: 992,
tablet: 768,
phone: 376
}
// Iterate through the sizes and create a media template
const media = Object.keys(sizes).reduce((acc, label) => {
acc[label] = (...args) => css`
#media (max-width: ${sizes[label] / 16}em) {
${css(...args)}
}
`
return acc
}, {})
const Content = styled.div`
height: 3em;
width: 3em;
background: papayawhip;
/* Now we have our methods on media and can use them instead of raw
queries */
${media.desktop`background: dodgerblue;`}
${media.tablet`background: mediumseagreen;`}
${media.phone`background: palevioletred;`}
`;
class App extends Component {
render() {
return (
<div className="App">
<div className="App-header">
header goes here!!!
</div>
<Content/>
</div>
);
}
}
export default App;
Nonetheless, I get the following error:
Line 14: 'css' is not defined no-undef
Line 16: 'css' is not defined no-undef
line 14 is the following: acc[label] = (...args) => css`
What's wrong with that line?
The link to the piece of code where I got this code is here
I'm sorry you're running into troubles. The only thing you need to change is to import the css helper from styled-components!
import styled, { css } from 'styled-components';
That will fix it.
I'd recommend reading our documentation so you're aware of the features the library has. It's not very long but it'll set you up for success. We'll also update the documentation to include the full import! (reference issue)

Javascript reactJs css in javascript in external file

I am using reactjs and trying to get me css inside javascript and finally read the css from an external js file.
Here is the code:
import React from 'react';
import ReactDOM from 'react-dom';
var styles = {
container: {
padding: 20,
border: '5px solid green',
borderRadius: 2
}
};
var myComponent = React.createClass({
render: function() {
return (
<div style={styles.container}>
{this.props.name}
</div>
);
}
});
This works fine but what I need to do is to put the css in an external file.
So I've created a file called general.css.js
And I tried to import it:
import styles from './components/general.css';
I add this import to the top of the page with the other imports.
The problem is that it's not reading the styles.
What I'm I doing wrong here?
Make a new file and put this code in it.
export const style = { container : {
padding: 20,
border: '5px solid green',
borderRadius: 2 }
};
Now in your component file.
import * as styles from './style/location/filename'
Now you can use styles in your render function.
return (
<div style={styles.style.main}>
<h3 style={styles.style.header}>Vote for your favorite hack day idea</h3>
</div>
);
You can directly import your css file in js.
import './style/app.css';
app.css
.page {
background-color:#fafafa;
}
and you can use this class in React component like below.
<div className="page">
Hope it works!!!!
There is a little tool that automates translation from CSS to JSON representation.
Worth checking that out.
Note how the translation adds _ underscores:
div.redcolor { color:red; }
div:hover { color:blue; }
Into:
{"div_redcolor":{"color":"red"},"div_hover":{"color":"blue"}}
Note how the Vishwas Chauhan used starred method of ES6 import export:
In case you use this tool you get one big object, and you can use any method.

Categories