How to determine if "click" or "box-select" was used with Streamlit/Plotly to return data from chart to Streamlit - python-3.x

I'm not a Javascript/Typescript/React dev. I'm hacking my way through this for a work project.
I'm using Streamlit, with plotly.
I'm hacking the basic code from streamlit-plotly-events.
I was trying to have the click or box-select information passed back with the data selected via the plotlyEventHandler() (see code below.) However, both this.props.args["click_event"] and this.props.args["select_event"] are true, regardless of whether you use box-select in the plotly chart, or click a single data point in the chart.
I thought of assuming if there is only one data point, then it was a click - but you can box select only one data point.
// import React, {useState,useEffect} from "react"
import React, { ReactNode } from "react"
//import React from "react"
import {
StreamlitComponentBase,
withStreamlitConnection,
Streamlit,
// ComponentProps,
} from "streamlit-component-lib"
import Plot from "react-plotly.js"
class StreamlitPlotlyEventsCapture extends StreamlitComponentBase {
public render = (): ReactNode => {
// Pull Plotly object from args and parse
const plot_obj = JSON.parse(this.props.args["plot_obj"]);
const override_height = this.props.args["override_height"];
const override_width = this.props.args["override_width"];
// Event booleans
const click_event = this.props.args["click_event"];
const select_event = this.props.args["select_event"];
const hover_event = this.props.args["hover_event"];
Streamlit.setFrameHeight(override_height);
return (
<Plot
data={plot_obj.data}
layout={plot_obj.layout}
config={plot_obj.config}
frames={plot_obj.frames}
onClick={click_event ? this.plotlyEventHandler : function(){}}
onSelected={select_event ? this.plotlyEventHandler : function(){}}
onHover={hover_event ? this.plotlyEventHandler : function(){}}
style={{width: override_width, height: override_height}}
className="stPlotlyChart"
/>
)
}
/** Click handler for plot. */
private plotlyEventHandler = (data: any) => {
// Build array of points to return
var clickedPoints: Array<any> = [];
//const util = require('util')//#33333 used with util.inspect(arrayItem) below
// I dont know why we can't directly use "this.variables" in the clickedPoints.push
// but we can't, so we create the variables here.
var wasClicked = this.props.args["click_event"];
var wasSelected = this.props.args["select_event"];
var wasHovered = this.props.args["hover_event"];
data.points.forEach(function (arrayItem: any) {
// console.log(util.inspect(arrayItem, {maxArrayLength: null, depth:null }))
clickedPoints.push({
// I dont know why we can't directly use "this.variables" here, but we can't
// so we use the variables created above.
clicked:wasClicked,
selected:wasSelected,
hovered:wasHovered,
x: arrayItem.x,
y: arrayItem.y,
curveNumber: arrayItem.curveNumber,
pointNumber: arrayItem.pointNumber,
pointIndex: arrayItem.pointIndex
})
});
// Return array as JSON to Streamlit
Streamlit.setComponentValue(JSON.stringify(clickedPoints))
}
}
export default withStreamlitConnection(StreamlitPlotlyEventsCapture)

Related

Meteor Tabular not reacting to ReactiveDict's values changing

I'm using the great Tabular package. https://github.com/Meteor-Community-Packages/meteor-tabular.
I'm making use of the client-side selector helper to Reactively change my table by having Server modify the query for my dataset.
I have multiple HTML inputs that act as filters and am populating a ReactiveDict with the values. A search-button click event triggers the ReactiveDict to get populated with an Object using .set
Initialization of ReactiveDict
Template.tbl.onCreated(function () {
const self = this;
self.filters = new ReactiveDict({});
});
Population of ReactiveDict
'click #search-button'(e, template) {
//clear to 'reset' fields in ReactiveDict that could've been cleared by User
template.filters.clear();
const searchableFields = getSearchableFields();
//Initialize standard JS Obj that ReactiveDict will then be set to
const filterObj = {};
//Loop through search fields on DOM and populate into Obj if they have a val
for (let field of searchableFields) {
const value = $(`#${field}-filter`).val();
if (value) {
filterObj[field] = new RegExp(escapeStringRegex(value.trim()), 'i');
}
}
if (Object.keys(filterObj).length) {
template.filters.set(filterObj);
}
},
Selector Helper
selector: () => {
const filters = Template.instance().filters.all();
const selector = { SOME_DEFAULT_OBJ, ...filters };
return selector;
},
I'm noticing the server doesn't notice any changes from a ReactiveDict if all keys remain the same.
I'm testing this by logging in the serve-side's changeSelector md and verifying that my logging does not occur if just a value in selector has changed.
Is there a solution to this?
I.e. {foo:'foo'} to {foo:'bar'} should reactively trigger the server to re-query but it does not. But {foo:'foo'} to {bar:'bar'} would get triggered.
Is this an issue with how I'm using the ReactiveDict or is this on the Tabular side?
Thanks

Redux Toolkit - Slice utility methods

I'm building a React app with redux-toolkit and I'm splitting my store into some slices with redux-toolkit's helper function createSlice.
Here it is a simple use case:
const sidebar = createSlice({
name: "sidebar",
initialState:
{
menus: {}, // Keep track of menus states (guid <-> open/close)
visible: true
},
reducers:
{
show(state, action)
{
state.visible = action.payload.visible;
},
setMenuOpen(state, action)
{
const { id, open } = action.payload;
state.menus[id] = open;
return state;
}
}
});
export default sidebar;
Everything works fine until I "add" actions (that change the store) to the slice but consider your team looking for an utility function "getMenuOpen": this method doesn't change the store (it's not an action and cannot be addeded to reducers object). You can of course read directly the data from the store (state.menus[<your_id>]) but consider a more complex example where manipulating the data requires some library imports, more complex code, etc...I want to modularize/hide each slice as much as possible.
Actually I'm using this workaround:
const sidebar = createSlice({ /* Same previous code... */ });
sidebar.methods =
{
getMenuOpen: (state, id) => state.menus[id]
};
export default sidebar;
The above code allows importing the slice from a component, mapStateToProps to the redux store, and invoke the utilty function getMenuOpen like this:
import sidebar from "./Sidebar.slice";
// Component declaration ...
const mapStateToProps = state => ({
sidebar: state.ui.layout.sidebar,
getMenuOpen(id)
{
return sidebar.methods.getMenuOpen(this.sidebar, id);
}
});
const mapDispatchToProps = dispatch => ({
setMenuOpen: (id, open) => dispatch(sidebar.actions.setMenuOpen({id, open}))
});
The ugly part is that I need to inject the slice node (this.sidebar) as fist param of getMenuOpen because it's not mapped (as for actions with reducers/actions) automatically from redux-toolkit.
So my question is: how can I clean my workaround in order to automatically map the store for utility functions? createSlice doesn't seem to support that but maybe some internal redux's api could help me in mapping my "slice.methods" automatically to the store.
Thanks

Svelte access a store variable in child component script tag

I have a Svelte & Sapper app where I am using a Svelte writable store to set up a variable with an initial blank string value:
import { writable } from 'svelte/store';
export let dbLeaveYear = writable('');
In my Index.svelte file I am importing this and then working out the value of this variable and setting it (I am doing this within the onMount function of ```Index.svelte if this is relevant):
<script>
import {dbLeaveYear} from "../stores/store.js"
function getCurrentLeaveYear() {
const today = new Date();
const currYear = today.getFullYear();
const twoDigitYear = currYear.toString().slice(-2);
const cutoffDate = `${twoDigitYear}-04-01`
const result = compareAsc(today, new Date(cutoffDate));
if (result === -1) {
$dbLeaveYear = `${parseInt(twoDigitYear, 10) - 1}${twoDigitYear}`;
} else {
$dbLeaveYear = `${twoDigitYear}${parseInt(twoDigitYear, 10) + 1}`;
}
}
onMount(() => {
getCurrentLeaveYear();
});
</script>
I have a child component being rendered in the Index.svelte
<Calendar />
Inside the Calendar child component I am importing the variable and trying to access it to perform a transform on it but I am getting errors that it is still blank - it is seemingly not picking up the assignment from Index.svelte:
<script>
import {dbLeaveYear} from "../stores/store.js"
const calStart = $dbLeaveYear.slice(0, 2)
</script>
However if I use the value in an HTML element in the same Calendar child component with <p>{$dbLeaveYear}</p> it is populated with the value from the calculation in Index.svelte.
How can I access the store variable inside the <script> tag of the child component? Is this even possible? I've tried assiging in onMount, I've tried assigning in a function - nothing seems to work and it always says that $dbLeaveYear is a blank string.
I need the value to be dynamic as the leave year value can change.
Before digging deeper into your problem, let me say that you shouldn't mutate the store variable directly, but use the provided set or update method. This avoids hard-to-debug bugs:
if (result === -1) {
dbLeaveYear.set(() => `${parseInt(twoDigitYear, 10) - 1}${twoDigitYear}`);
} else {
dbLeaveYear.set(`${twoDigitYear}${parseInt(twoDigitYear, 10) + 1}`);
}
With that out of the way, the problem seems to be that your auto-subscribe to the store is not ideal for your use case. You need to use the subscribe property for that:
<script>
import { dbLeaveYear } from "../stores/store.js"
import { onDestroy, onMount } from "svelte"
let yearValue;
// Needed to avoid memory leaks
let unsubscribe
onMount(() => {
unsubscribe = dbLeaveYear.subscribe(value => yearValue = value.slice(0, 2));
})
onDestroy(unsubscribe);
</script>
Another thing that could cause your problem is a race condition. So the update from the parent component is not finished when the child renders. Then you would need to add a sanity check in the rendering child component.
The answer here is a combination of Sapper preload and the ability to export a function from a store.
in store.js export the writable store for the variable you want and also a function that will work out the value and set the writable store:
export let dbLeaveYear = writable('');
export function getCurrentLeaveYear() {
const today = new Date();
const currYear = today.getFullYear();
const twoDigitYear = currYear.toString().slice(-2);
const cutoffDate = `${twoDigitYear}-04-01`
const result = compareAsc(today, new Date(cutoffDate));
if (result === -1) {
dbLeaveYear.set(`${parseInt(twoDigitYear, 10) - 1}${twoDigitYear}`);
} else {
dbLeaveYear.set(`${twoDigitYear}${parseInt(twoDigitYear, 10) + 1}`);
}
}
In the top-level .svelte file, use Sapper's preload() function inside a "module" script tag to call the function that will work out the value and set the writable store:
<script context="module">
import {getCurrentLeaveYear} from '../stores/store'
export async function preload() {
getCurrentLeaveYear();
}
</script>
And then in the component .svelte file, you can import the store variable and because it has been preloaded it will be available in the <script> tag:
<script>
import {dbLeaveYear} from '../stores/store'
$: startDate = `20${$dbLeaveYear.slice(0, 2)}`
$: endDate = `20${$dbLeaveYear.slice(-2)}`
</script>

How do I get the input text (not just the value) from a Material for Web select component?

In the docs, it specifies how to get the index and data-value, but not the input text:
import {MDCSelect} from '#material/select';
const select = new MDCSelect(document.querySelector('.mdc-select'));
select.listen('MDCSelect:change', () => {
alert(`Selected option at index ${select.selectedIndex} with value "${select.value}"`);
});
The following assumes you have more than one MDCSelect to initiate
import {MDCSelect} from '#material/select';
const selectElements = [].slice.call(document.querySelectorAll('.mdc-select'));
selectElements.forEach((selectEl) => {
const select = new MDCSelect(selectEl);
select.listen('MDCSelect:change', (el) => {
const elText = el.target.querySelector(`[data-value="${select.value}"`).innerText;
console.log(`Selected option at index ${select.selectedIndex} with value "${select.value}" with a label of ${elText}`);
});
});

How to work around the require(“../../../../../../../”) frustration In NodeJS?

Any better ways of solving this kind of problem in node.js below?
import foo from "../../../modules/home/models/index.js"
import bar from "../../../modules/about/models/index.js"
import baz from "../../../modules/contact/models/index.js"
At least making them into something like this?
import foo from "/home/models/index.js"
import bar from "/about/models/index.js"
import baz from "/contact/models/index.js"
Any ideas?
You need inversion of control.
./modules/home/index.js
const homeModel1 = () => {
//...
}
const homeModel2 = () => {
//...
}
module.exports = Object.assign({}, { homeModel1, homeModel2 })
1. An object will be exported of the following shape:
{
homeModel1: () => {},
homeModel2: () => {}
}
2. When you add a new model, simply add it or import it into this file and then add it to the export object.
./modules/index.js
import { homeModels } from './modules/home'
import { aboutModels } from './modules/about'
import { contactModels } from './modules/contact'
module.exports = Object.assign({}, { homeModels, aboutModels, contactModels })
The models are destructured out and then exported as methods on a new object.
Likewise, same shape object is exported with all your models cultivated together, bringing all their dependencies with them.
somewhere else
import modules from './modules'
const query = modules.homeModels.homeModel1()
Bonus:
To clarify, Object.assign({}, obj1, obj2) creates a new object with the prototype set to the Object prototype, and merges the properties and methods of obj1 and obj2. In this simple form, it is essentially the same as const obj = {}.
A bit more advanced, is Object.assign({}, { obj1, obj2 }) which makes obj1 and obj2 properties on the new object. You can do some simple testing to get a feel for the data structures.
We also used some destructuring. If you are having issues getting things lined up properly, you should look at those aspects plus how you are importing them into a file. For example, import obj1 from './modules' will bring the entire object in from ./modules, but import { obj1 } from './modules' will destructure obj1 from the object that it pulls in, so obj1 was a method/property of the object.
Do some research into inversion of control and dependency injection.

Resources