Creating tooltip using callback for dash DataTable - plotly-dash

I am trying to create tooltip for a dash data_table using callback. But my multiple attempts are unsuccessful.
I have seen examples where tooltip is create by reading a csv from a path. But in my case dataframe is created within the callback function and returned after clicking a submit button. Below is the code I am using
display_cols=["col1","col2","col3","col4"]
columns_property=[{"name": i, "id": i, "deletable": False, "selectable": True, "renamable":True, "hideable":True} for i in display_cols]
dash_table.DataTable(id="table",
columns=columns_property,data=[],fill_width=True,
export_columns="all",export_format="xlsx", sort_action="native",is_focused=True,
sort_mode="multi",export_headers ="names",editable=True,tooltip_data=tooltip,## Tootlip is returned from callback as options
style_cell={'textAlign': 'left','border': '1px solid grey',
'whiteSpace':'normal','height':'auto'},
style_header={'backgroundColor': 'white','fontWeight': 'bold',
'border': '1px solid black'},
style_table={'fontFamily': 'Open Sans',
'textAlign': 'right',
'whiteSpace': 'no-wrap',
'overflowX': 'scroll',
'minWidth': '100%',
'height': '600px',
'overflowY': 'scroll'})
#app.callback([Output('table', 'data'),Output("tooltip", "options") ],
[Input('submit3', 'n_clicks')],
[
State('input1', 'value'),
State('input2', 'value')
]
)
def update_output(clicked, input1, input2):
if clicked:
input_file=input1
model_path=input2
""" Some Code for Generatng DF"""
df=df[["col1","col2","col3","col4"]]
tooltip_data= [{c:{'type': 'text', 'value': f'{r},{c}'} for c in df.columns} for r in df[df.columns].values]
return list(df.to_dict("index").values()), tooltip_data

So if when you have a question, it is helpful if the code you provide can be run on its own. Looks like the code you provided was part of a larger project and was missing many required calls and boilerplate type stuff to work.
By making your code into a runable Dash app, I may have fixed issues inadvertently. Also, I find that having each function argument to dash_table.DataTable() on its own line makes it easier to read, troubleshoot, and verify. As there are about a bazillion arguments, it can get pretty crazy.
I've found the Dash DataTable documentation to be pretty good. I recommend reading through this to see various ways to use and setup tooltips:
https://dash.plotly.com/datatable/tooltips
You've provided a use case where the tooltips are dependent on a callback. Callbacks can be complicated as they are triggered off a change in the page (changing typically meaning input from the user). Callbacks are run on initial app start up and then every time the specified input changes.
Although callbacks are run on app start, you cannot have the output of the callback listed in the object definition. So, this portion:
dash_table.DataTable(#...
tooltip_data=tooltip,## Tootlip is returned from callback as options
#...
Results in a syntax error as tooltip is not defined. The callback configuration will specify where the callback return value(s) go. In this case you have no tooltips to start with, so the dash_table.DataTable() should not have the argument specified.
Here is a version of your code tweaked to show tooltips.
import dash
import dash_core_components as dcc
import dash_html_components as html
import plotly.graph_objects as go
import pandas as pd
import numpy as np
import dash_bootstrap_components as dbc
from dash.dependencies import Input, Output, State
import dash_table
app = dash.Dash(__name__)
display_cols=["col1","col2","col3","col4"]
columns_property=[{"name": i, "id": i, "deletable": False, "selectable": True, "renamable":True, "hideable":True} for i in display_cols]
app.layout = html.Div(children=[
html.Div(
dcc.Input(
id="submit3",
type='number'
)
),
dash_table.DataTable(id="table",
columns=columns_property,
data=[],
fill_width=True,
export_columns="all",
export_format="xlsx",
sort_action="native",
is_focused=True,
sort_mode="multi",
export_headers ="names",
editable=True,
style_cell={'textAlign': 'left','border': '1px solid grey',
'whiteSpace':'normal','height':'auto'},
style_header={'backgroundColor': 'white','fontWeight': 'bold',
'border': '1px solid black'},
style_table={'fontFamily': 'Open Sans',
'textAlign': 'right',
'whiteSpace': 'no-wrap',
'overflowX': 'scroll',
'minWidth': '100%',
'height': '600px',
'overflowY': 'scroll'})
])
#app.callback([Output('table', 'data'),
Output("table", "tooltip_data")],
[Input('submit3', 'value')]
)
def update_output(input1):
""" Some Code for Generatng DF"""
df=pd.DataFrame(np.random.randint(0,10,size=(10, 4)), columns=['col1', 'col2', 'col3', 'col4'])
# tooltips
tooltip_data= [{c:{'type': 'text', 'value': f'{r},{c}'} for c in df.columns} for r in df[df.columns].values]
return list(df.to_dict("index").values()), tooltip_data
if __name__ == '__main__':
app.run_server(debug=True)

Related

How to return HTML / components from clientside callbacks?

I want to convert a regular (Python) callback to clientside. I'm running into difficulties with outputting to children property anything more complicated then a simple string.
The hope was that I could create a new component with new dash_html_components.Span(), but this throws an error:
Error: An object was provided as `children` instead of a component, string, or number (or list of those). Check the children property that looks something like:
{
"type": "span",
"key": null,
"ref": null,
"props": {
"children": "You have clicked THE Button!"
},
"_owner": null
}
The full code is below. It is possible to set anything other than a string into children like this, or what am I doing wrong?
import dash_html_components as html
from dash import Dash
from dash.dependencies import Output, Input
app = Dash(prevent_initial_callbacks=True)
app.layout = html.Div([
html.Button("THE Button", id="the_button"),
html.Div(id="the_log"),
])
app.clientside_callback(
"""
function(n_clicks){
// return "You have clicked THE Button!"; // works
// return "You have clicked <strong>THE Button!</strong>"; // works, but escapes the HTML (as expected)
return new dash_html_components.Span({children: "You have clicked THE Button!"}); // doesn't work
// return ["You have clicked ", new dash_html_components.Strong({children: "THE Button!" })]; // the goal
}
""",
Output("the_log", "children"),
Input("the_button", "n_clicks"),
)
if __name__ == '__main__':
app.run_server()
If you just need to write inline HTML code, you could use the Purify component from dash-extensions. When you pass HTML code to its html property, it is rendered inline,
from dash import html, Dash, Output, Input
from dash_extensions import Purify
app = Dash(prevent_initial_callbacks=True)
app.layout = html.Div([html.Button("Click me", id="btn"), Purify(id="purify")])
app.clientside_callback("""function(n_clicks){return 'This is <b>html</b>';}""",
Output("purify", "html"), Input("btn", "n_clicks"))
if __name__ == "__main__":
app.run_server()
Prior to rendering, sanitization is performed using DOMPurify, hence the name of the component.
After some debugging, I found this to work:
function(n_clicks){
return [
"You have clicked ",
{
type: "Strong",
namespace: "dash_html_components",
props: {children: "THE Button"},
},
"!"
];
}

properly import default export from JSON module

I have a JSON translation file "./countries/es.json" in my Angular application that I want to import and loop through.
{
...
"Solomon Islands": "Islas Salomón",
"South Sudan": "Sudán del Sur",
...
}
I have "resolveJsonModule": true, in my tsconfig.json file, so in general import goes well.
import * as countries from './countries/es.json';
and if I access the object say countries['Solomon Islands'] I get 'Islas Salomón' that is correct.
However if want to enumerate all countries:
const countries_keys = Object.keys(countries);
countries_keys is an array with one value 'default'. In order to obtain the country list I need to do:
const countries_keys = Object.keys(countries['default']);
My question - is there a way to do the import cleanly so I get the object exactly as in JSON file?
If I had the source file like:
{
countries: {
...
"Solomon Islands": "Islas Salomón",
"South Sudan": "Sudán del Sur",
...
}
}
I could simply do:
import { countries } from './countries/es.json';
but is there a clean way to import my original file as equivalent JSON object without the additional 'default' property.
You need add allowSyntheticDefaultImports in your tsconfig.json.
tsconfig.json
{
"allowSyntheticDefaultImports": true,
"resolveJsonModule": true
}
TS
import countries from './countries/es.json';
import is not a good idea here later on if you want to move your file to some server you will need to rewrite the whole logic i would suggest to use httpclient get call here.So move you file to assets folder and then
constructor(private http:HttpClient){
this.http.get('assets/yourfilepath').subscribe(data=>{
const countries_keys = Object.keys(data['countries']);
console.log(data['countries'])//data is your json object here
})
}
What worked form me was:
import countries from './countries/es.json';
This is the "default import"

Simple http operator headers value jinja template not supported

I am trying to use templating in headers value of my http request using my custom http operator ( Extends simpleHttpOperator ). Seems like templating is supported only in data field. How can implemente the same in headers field. I wanted to pass authorization header templated. Please find my code below.
import airflow
from airflow import DAG
from airflow.configuration import conf
from airflow.operators.python_operator import PythonOperator
from airflow.operators.auth_plugins import SipAuthOperator
from airflow.operators.util_plugins import AuthUtility
DEFAULT_VERSION = '2.0'
default_args = {
'owner': 'airflow',
'depends_on_past': False,
'start_date': airflow.utils.dates.days_ago(2),
'email': ['airflow#example.com'],
'email_on_failure': False,
'email_on_retry': False
}
DAG_ID = 'test_dag'
dag = DAG(DAG_ID, default_args=default_args,
schedule_interval=None,
catchup=False)
dag.doc_md = __doc__
auth_endpoint = conf['auth_apis']['authenticate_end_point']
def inspect_params(**context):
token = context['task_instance'].xcom_push(key='JWT_TOKEN',value='helloooo'
)
print(token)
test_operator = PythonOperator(dag=dag,task_id='init_api',
python_callable=inspect_params,
provide_context=True, )
# data={'token':'{{task_instance.xcom_pull(key=\'JWT_TOKEN\')}}'}
# {'Authorization':'Bearer '+'{{task_instance.xcom_pull(key=\'JWT_TOKEN\')}}'
http_operator = SipAuthOperator( dag=dag,task_id='authenticate_api',http_conn_id='auth_api',endpoint=auth_endpoint,headers={'token':'{{task_instance.xcom_pull(key=\'JWT_TOKEN\')}}'})
test_operator >> http_operator
Header value coming as {'token': "{{task_instance.xcom_pull(key='JWT_TOKEN')}}"} which is not as desred. If I put the same value in data field it works fine as expected. Is jinja templating supported on headers ? Any work around for this issue ?
The template_fields attribute in an operator determines which parameters can be templated. For example, in the original SimpleHttpOperator you can see the following
class SimpleHttpOperator(BaseOperator):
...
template_fields = ('endpoint', 'data',)
...
Which is why endpoint and data are supported template fields. Similarly in your custom operator, you'll want to include header.
class SipAuthOperator(SimpleHttpOperator):
...
template_fields = ('endpoint', 'data', 'header',)
...

How do I create a "fat" js file with rollup using esm?

I have the following code..
// ui.js (generated by rollup
import Vue from 'vue';
import VueRouter from 'vue-router';
(()=>{
console.log("Wow it actually works");
Vue.use(VueRouter);
const routes = [
{
path: '/',
component: Viewport
}
];
const router = new VueRouter({
mode: "history",
routes: routes
});
window.app = new Vue({ router });
window.app.$mount('#jg-app');
})();
<script src="ui.js" type="module"> </script>
The problem is when I run this I get...
Uncaught TypeError: Failed to resolve module specifier "vue". Relative references must start with either "/", "./", or "../".
This leads me to believe I need a "fat" js that includes dependencies.
I also want to keep everything in es6 modules and avoid introducing say babel.
Is there a way to do this using rollup?
Update
Tried this...
import Vue from "./vue";
But then I get...
Error: Could not resolve './vue' from src/index.js
As far as I can tell this is not possible. I instead had to move the import from the ui project to the server project and create a static js file that looked like this...
//client
import Vue from "./vue"
let app = new Vue(...);
app.$mount('#jg-app');
and import the esm.browser version
// server
app.use('/vue', express.static(__dirname + '/node_modules/vue/dist/vue.esm.browser.js'));
// template
script(src="/main.js" type="module")
Now Vue is working, however, dependencies like Vue-Router appear to not have this es.browser style file.
This is not a solution, it's a workaround
The below rollup config is not esm, it's just a way to create a bundle with dependencies included.
You get one minified browser-compatible JS file.
Here's my working example rollup.config.js (you should replace input: 'src/index.js' with your web app entry point and output.file with a location for the generated bundle):
import resolve from 'rollup-plugin-node-resolve';
import commonjs from 'rollup-plugin-commonjs';
import builtins from 'rollup-plugin-node-builtins';
import babel from 'rollup-plugin-babel';
import visualizer from 'rollup-plugin-visualizer';
import { terser } from "rollup-plugin-terser";
const browserPlugins = [
resolve({browser: true}), // so Rollup can properly resolve cuid
babel({
exclude: 'node_modules/**',
babelrc: false,
presets: ['es2015-rollup'],
}),
// builtins(),
commonjs(),
visualizer(),
terser(),
]
export default [
// browser-friendly UMD build
{
// external: Object.keys(globals),
input: 'src/index.js',
output: {
name: 'thinflux',
file: './dist/browser/thinflux.min.js',
format: 'umd'
},
plugins: browserPlugins,
}
];
One more thing: express should statically serve the output.file path, not your source files

bundling CesiumJS using RollupJS

I am trying to bundle CesiumJS with Rollup. I thought I could just do an import like this:
import Cesium from 'cesium/Build/Cesium/Cesium.js'
with the following rollup.config.js file. I am getting a bundle.js but when I run it I get lots errors:
Uncaught TypeError: Cannot read property 'document' of undefined
at bundle.js:formatted:102314
function() {
!function(e) {
var t = this || eval("this")
, r = t.document // it is complaining on this line
, i = t.navigator
, n = t.jQuery
, o = t.JSON;
rollup.config.js
import resolve from 'rollup-plugin-node-resolve'
import commonjs from 'rollup-plugin-commonjs'
import uglify from 'rollup-plugin-uglify'
import { minify } from 'uglify-es'
export default {
input: 'scripts/Main.js',
output: {
file: 'dist/bundle.js',
format: 'es',
},
"options": {
sourceMap: 'inline',
output: {
format: 'es'
}
},
plugins: [
resolve({
jsnext: true,
main: true,
browser: true,
}),
commonjs(),
uglify({}, minify)
]
}
ES modules are always in strict mode — by extension, when something is imported into Rollup and converted to an ES module, it also runs in strict mode.
In strict mode, the value of this inside a function is undefined, unless it's a) called as a method, or b) explicitly set with call or apply.
This is expected behaviour with Rollup, and it isn't technically a bug with Cesium, but I would suggest raising an issue with them and seeing if they can use a more modern way of accessing the global variable. There's really no reason to be relying on non-strict behaviour in 2017!
As a last resort you could string-replace this || eval("this") (or this||(0,eval)("this"), as it is in the minified version) with window.
If there are lots of other errors after making that change, it may be impossible to include Cesium in your bundle, and you would need to keep it as an external module.