123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742 |
- /*
- * Copyright 2021-2024 Avaiga Private Limited
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
- * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations under the License.
- */
- import React, { CSSProperties, lazy, Suspense, useCallback, useEffect, useMemo, useRef, useState } from "react";
- import { useTheme } from "@mui/material";
- import Box from "@mui/material/Box";
- import Skeleton from "@mui/material/Skeleton";
- import Tooltip from "@mui/material/Tooltip";
- import merge from "lodash/merge";
- import { nanoid } from "nanoid";
- import {
- Config,
- Data,
- Layout,
- ModeBarButtonAny,
- PlotDatum,
- PlotMarker,
- PlotRelayoutEvent,
- PlotSelectionEvent,
- ScatterLine,
- } from "plotly.js";
- import { Figure } from "react-plotly.js";
- import {
- createRequestChartUpdateAction,
- createSendActionNameAction,
- createSendUpdateAction,
- } from "../../context/taipyReducers";
- import { lightenPayload } from "../../context/wsUtils";
- import { darkThemeTemplate } from "../../themes/darkThemeTemplate";
- import {
- useClassNames,
- useDispatch,
- useDispatchRequestUpdateOnFirstRender,
- useDynamicJsonProperty,
- useDynamicProperty,
- useModule,
- } from "../../utils/hooks";
- import { ColumnDesc } from "./tableUtils";
- import { getComponentClassName } from "./TaipyStyle";
- import { getArrayValue, getUpdateVar, TaipyActiveProps, TaipyChangeProps } from "./utils";
- const Plot = lazy(() => import("react-plotly.js"));
- interface ChartProp extends TaipyActiveProps, TaipyChangeProps {
- title?: string;
- width?: string | number;
- height?: string | number;
- defaultConfig: string;
- config?: string;
- data?: Record<string, TraceValueType>;
- defaultLayout?: string;
- layout?: string;
- plotConfig?: string;
- onRangeChange?: string;
- render?: boolean;
- defaultRender?: boolean;
- template?: string;
- template_Dark_?: string;
- template_Light_?: string;
- //[key: `selected_${number}`]: number[];
- figure?: Array<Record<string, unknown>>;
- onClick?: string;
- }
- interface ChartConfig {
- columns: Record<string, ColumnDesc>;
- labels: string[];
- modes: string[];
- types: string[];
- traces: string[][];
- xaxis: string[];
- yaxis: string[];
- markers: Partial<PlotMarker>[];
- selectedMarkers: Partial<PlotMarker>[];
- orientations: string[];
- names: string[];
- lines: Partial<ScatterLine>[];
- texts: string[];
- textAnchors: string[];
- options: Record<string, unknown>[];
- axisNames: Array<string[]>;
- addIndex: Array<boolean>;
- decimators?: string[];
- }
- export type TraceValueType = Record<string, (string | number)[]>;
- const defaultStyle = { position: "relative", display: "inline-block" };
- const indexedData = /^(\d+)\/(.*)/;
- export const getColNameFromIndexed = (colName: string): string => {
- if (colName) {
- const reRes = indexedData.exec(colName);
- if (reRes && reRes.length > 2) {
- return reRes[2] || colName;
- }
- }
- return colName;
- };
- export const getValue = <T,>(
- values: TraceValueType | undefined,
- arr: T[],
- idx: number,
- returnUndefined = false
- ): (string | number)[] | undefined => {
- const value = getValueFromCol(values, getArrayValue(arr, idx) as unknown as string);
- if (!returnUndefined || value.length) {
- return value;
- }
- return undefined;
- };
- export const getValueFromCol = (values: TraceValueType | undefined, col: string): (string | number)[] => {
- if (values) {
- if (col) {
- // TODO: Re-review the logic here
- if (Array.isArray(values)) {
- const reRes = indexedData.exec(col);
- if (reRes && reRes.length > 2) {
- return values[parseInt(reRes[1], 10) || 0][reRes[2] || col] || [];
- }
- } else {
- return values[col] || [];
- }
- }
- }
- return [];
- };
- export const getAxis = (traces: string[][], idx: number, columns: Record<string, ColumnDesc>, axis: number) => {
- if (traces.length > idx && traces[idx].length > axis && traces[idx][axis] && columns[traces[idx][axis]])
- return columns[traces[idx][axis]].dfid;
- return undefined;
- };
- const getDecimatorsPayload = (
- decimators: string[] | undefined,
- plotDiv: HTMLDivElement | null,
- modes: string[],
- columns: Record<string, ColumnDesc>,
- traces: string[][],
- relayoutData?: PlotRelayoutEvent
- ) => {
- return decimators
- ? {
- width: plotDiv?.clientWidth,
- height: plotDiv?.clientHeight,
- decimators: decimators.map((d, i) =>
- d
- ? {
- decimator: d,
- xAxis: getAxis(traces, i, columns, 0),
- yAxis: getAxis(traces, i, columns, 1),
- zAxis: getAxis(traces, i, columns, 2),
- chartMode: modes[i],
- }
- : {
- xAxis: getAxis(traces, i, columns, 0),
- yAxis: getAxis(traces, i, columns, 1),
- zAxis: getAxis(traces, i, columns, 2),
- chartMode: modes[i],
- }
- ),
- relayoutData: relayoutData,
- }
- : undefined;
- };
- const selectedPropRe = /selected(\d+)/;
- const MARKER_TO_COL = ["color", "size", "symbol", "opacity", "colors"];
- const isOnClick = (types: string[]) => (types?.length ? types.every((t) => t === "pie") : false);
- interface Axis {
- p2c: () => number;
- p2d: (a: number) => number;
- }
- interface PlotlyMap {
- _subplot?: { xaxis: Axis; yaxis: Axis };
- }
- interface PlotlyDiv extends HTMLDivElement {
- _fullLayout?: {
- map?: PlotlyMap;
- geo?: PlotlyMap;
- mapbox?: PlotlyMap;
- xaxis?: Axis;
- yaxis?: Axis;
- };
- }
- interface WithPointNumbers {
- pointNumbers: number[];
- }
- export const getPlotIndex = (pt: PlotDatum) =>
- pt.pointIndex === undefined
- ? pt.pointNumber === undefined
- ? (pt as unknown as WithPointNumbers).pointNumbers?.length
- ? (pt as unknown as WithPointNumbers).pointNumbers[0]
- : 0
- : pt.pointNumber
- : pt.pointIndex;
- const defaultConfig = {
- columns: {} as Record<string, ColumnDesc>,
- labels: [],
- modes: [],
- types: [],
- traces: [],
- xaxis: [],
- yaxis: [],
- markers: [],
- selectedMarkers: [],
- orientations: [],
- names: [],
- lines: [],
- texts: [],
- textAnchors: [],
- options: [],
- axisNames: [],
- addIndex: [],
- } as ChartConfig;
- const emptyLayout = {} as Partial<Layout>;
- const emptyData = {} as Record<string, TraceValueType>;
- export const TaipyPlotlyButtons: ModeBarButtonAny[] = [
- {
- name: "Full screen",
- title: "Full screen",
- icon: {
- height: 24,
- width: 24,
- path: "M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z",
- },
- click: function (gd: HTMLElement, evt: Event) {
- const div = gd.querySelector("div.svg-container") as HTMLDivElement;
- if (!div) {
- return;
- }
- const { height, width } = gd.dataset;
- if (!height) {
- const st = getComputedStyle(div);
- gd.setAttribute("data-height", st.height);
- gd.setAttribute("data-width", st.width);
- }
- const fs = gd.classList.toggle("full-screen");
- (evt.currentTarget as HTMLElement).setAttribute("data-title", fs ? "Exit Full screen" : "Full screen");
- if (!fs) {
- // height && div.attributeStyleMap.set("height", height);
- height && (div.style.height = height);
- // width && div.attributeStyleMap.set("width", width);
- width && (div.style.width = width);
- }
- window.dispatchEvent(new Event("resize"));
- },
- },
- ];
- const updateArrays = (sel: number[][], val: number[], idx: number) => {
- if (idx >= sel.length || val.length !== sel[idx].length || val.some((v, i) => sel[idx][i] != v)) {
- sel = sel.concat(); // shallow copy
- sel[idx] = val;
- }
- return sel;
- };
- const getDataKey = (columns: Record<string, ColumnDesc>, decimators?: string[]): [string[], string] => {
- const backCols = Object.values(columns).map((col) => col.dfid);
- return [backCols, backCols.join("-") + (decimators ? `--${decimators.join("")}` : "")];
- };
- const Chart = (props: ChartProp) => {
- const {
- title = "",
- width = "100%",
- height,
- updateVarName,
- updateVars,
- id,
- data = emptyData,
- onRangeChange,
- propagate = true,
- onClick,
- } = props;
- const dispatch = useDispatch();
- const [selected, setSelected] = useState<number[][]>([]);
- const plotRef = useRef<HTMLDivElement>(null);
- const [dataKey, setDataKey] = useState("__default__");
- const lastDataPl = useRef<Data[]>([]);
- const theme = useTheme();
- const module = useModule();
- const refresh = useMemo(() => (data?.__taipy_refresh !== undefined ? nanoid() : false), [data]);
- const className = useClassNames(props.libClassName, props.dynamicClassName, props.className);
- const active = useDynamicProperty(props.active, props.defaultActive, true);
- const render = useDynamicProperty(props.render, props.defaultRender, true);
- const hover = useDynamicProperty(props.hoverText, props.defaultHoverText, undefined);
- const baseLayout = useDynamicJsonProperty(props.layout, props.defaultLayout || "", emptyLayout);
- // get props.selected[i] values
- useEffect(() => {
- if (props.figure) {
- return;
- }
- setSelected((sel) => {
- Object.keys(props).forEach((key) => {
- const res = selectedPropRe.exec(key);
- if (res && res.length == 2) {
- const idx = parseInt(res[1], 10);
- let val = (props as unknown as Record<string, number[]>)[key];
- if (val !== undefined) {
- if (typeof val === "string") {
- try {
- val = JSON.parse(val) as number[];
- } catch {
- // too bad
- val = [];
- }
- }
- if (!Array.isArray(val)) {
- val = [];
- }
- if (idx === 0 && val.length && Array.isArray(val[0])) {
- for (let i = 0; i < val.length; i++) {
- sel = updateArrays(sel, val[i] as unknown as number[], i);
- }
- } else {
- sel = updateArrays(sel, val, idx);
- }
- }
- }
- });
- return sel;
- });
- }, [props]);
- const config = useDynamicJsonProperty(props.config, props.defaultConfig, defaultConfig);
- useEffect(() => {
- if (updateVarName) {
- const [backCols, dtKey] = getDataKey(config.columns, config.decimators);
- setDataKey(dtKey);
- if (refresh || !data[dtKey]) {
- dispatch(
- createRequestChartUpdateAction(
- updateVarName,
- id,
- module,
- backCols,
- dtKey,
- getDecimatorsPayload(
- config.decimators,
- plotRef.current,
- config.modes,
- config.columns,
- config.traces
- )
- )
- );
- }
- }
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [refresh, dispatch, config.columns, config.traces, config.modes, config.decimators, updateVarName, id, module]);
- useDispatchRequestUpdateOnFirstRender(dispatch, id, module, updateVars);
- const layout = useMemo(() => {
- const layout = { ...baseLayout };
- let template = undefined;
- try {
- const tpl = props.template && JSON.parse(props.template);
- const tplTheme =
- theme.palette.mode === "dark"
- ? props.template_Dark_
- ? JSON.parse(props.template_Dark_)
- : darkThemeTemplate
- : props.template_Light_ && JSON.parse(props.template_Light_);
- template = tpl ? (tplTheme ? { ...tpl, ...tplTheme } : tpl) : tplTheme ? tplTheme : undefined;
- } catch (e) {
- console.info(`Error while parsing Chart.template\n${(e as Error).message || e}`);
- }
- if (template) {
- layout.template = template;
- }
- if (props.figure) {
- return merge({}, props.figure[0].layout as Partial<Layout>, layout, {
- title: title || layout.title || (props.figure[0].layout as Partial<Layout>).title,
- clickmode: "event+select",
- });
- }
- return {
- ...layout,
- autosize: true,
- title: title || layout.title,
- xaxis: {
- title:
- config.traces.length && config.traces[0].length && config.traces[0][0]
- ? getColNameFromIndexed(config.columns[config.traces[0][0]]?.dfid)
- : undefined,
- ...layout.xaxis,
- },
- yaxis: {
- title:
- config.traces.length == 1 && config.traces[0].length > 1 && config.columns[config.traces[0][1]]
- ? getColNameFromIndexed(config.columns[config.traces[0][1]]?.dfid)
- : undefined,
- ...layout.yaxis,
- },
- clickmode: "event+select",
- } as Layout;
- }, [
- theme.palette.mode,
- title,
- config.columns,
- config.traces,
- baseLayout,
- props.template,
- props.template_Dark_,
- props.template_Light_,
- props.figure,
- ]);
- const style = useMemo(
- () =>
- height === undefined
- ? ({ ...defaultStyle, width: width } as CSSProperties)
- : ({ ...defaultStyle, width: width, height: height } as CSSProperties),
- [width, height]
- );
- const skelStyle = useMemo(() => ({ ...style, minHeight: "7em" }), [style]);
- const dataPl = useMemo(() => {
- if (props.figure) {
- return lastDataPl.current;
- }
- if (data.__taipy_refresh !== undefined) {
- return lastDataPl.current || [];
- }
- const dtKey = getDataKey(config.columns, config.decimators)[1];
- if (!dataKey.startsWith(dtKey)) {
- return lastDataPl.current || [];
- }
- const datum = data[dataKey];
- lastDataPl.current = datum
- ? config.traces.map((trace, idx) => {
- const ret = {
- ...getArrayValue(config.options, idx, {}),
- type: config.types[idx],
- mode: config.modes[idx],
- name:
- getArrayValue(config.names, idx) ||
- (config.columns[trace[1]] ? getColNameFromIndexed(config.columns[trace[1]].dfid) : undefined),
- } as Record<string, unknown>;
- ret.marker = { ...getArrayValue(config.markers, idx, ret.marker || {}) };
- if (Object.keys(ret.marker as object).length) {
- MARKER_TO_COL.forEach((prop) => {
- const val = (ret.marker as Record<string, unknown>)[prop];
- if (typeof val === "string") {
- const arr = getValueFromCol(datum, val as string);
- if (arr.length) {
- (ret.marker as Record<string, unknown>)[prop] = arr;
- }
- }
- });
- } else {
- delete ret.marker;
- }
- const xs = getValue(datum, trace, 0) || [];
- const ys = getValue(datum, trace, 1) || [];
- const addIndex = getArrayValue(config.addIndex, idx, true) && !ys.length;
- const baseX = addIndex ? Array.from(Array(xs.length).keys()) : xs;
- const baseY = addIndex ? xs : ys;
- const axisNames = config.axisNames.length > idx ? config.axisNames[idx] : ([] as string[]);
- if (baseX.length) {
- if (axisNames.length > 0) {
- ret[axisNames[0]] = baseX;
- } else {
- ret.x = baseX;
- }
- }
- if (baseY.length) {
- if (axisNames.length > 1) {
- ret[axisNames[1]] = baseY;
- } else {
- ret.y = baseY;
- }
- }
- const baseZ = getValue(datum, trace, 2, true);
- if (baseZ) {
- if (axisNames.length > 2) {
- ret[axisNames[2]] = baseZ;
- } else {
- ret.z = baseZ;
- }
- }
- // Hack for treemap charts: create a fallback 'parents' column if needed
- // This works ONLY because 'parents' is the third named axis
- // (see __CHART_AXIS in gui/utils/chart_config_builder.py)
- else if (config.types[idx] === "treemap" && Array.isArray(ret.labels)) {
- ret.parents = Array(ret.labels.length).fill("");
- }
- // Other axis
- for (let i = 3; i < axisNames.length; i++) {
- ret[axisNames[i]] = getValue(datum, trace, i, true);
- }
- ret.text = getValue(datum, config.texts, idx, true);
- ret.xaxis = config.xaxis[idx];
- ret.yaxis = config.yaxis[idx];
- ret.hovertext = getValue(datum, config.labels, idx, true);
- const selPoints = getArrayValue(selected, idx, []);
- if (selPoints?.length) {
- ret.selectedpoints = selPoints;
- }
- ret.orientation = getArrayValue(config.orientations, idx);
- ret.line = getArrayValue(config.lines, idx);
- ret.textposition = getArrayValue(config.textAnchors, idx);
- const selectedMarker = getArrayValue(config.selectedMarkers, idx);
- if (selectedMarker) {
- ret.selected = { marker: selectedMarker };
- }
- return ret as Data;
- })
- : lastDataPl.current || [];
- return lastDataPl.current;
- }, [props.figure, selected, data, config, dataKey]);
- const plotConfig = useMemo(() => {
- let plConf: Partial<Config> = {};
- if (props.plotConfig) {
- try {
- plConf = JSON.parse(props.plotConfig);
- } catch (e) {
- console.info(`Error while parsing Chart.plot_config\n${(e as Error).message || e}`);
- }
- if (typeof plConf !== "object" || plConf === null || Array.isArray(plConf)) {
- console.info("Error Chart.plot_config is not a dictionary");
- plConf = {};
- }
- }
- plConf.displaylogo = !!plConf.displaylogo;
- plConf.modeBarButtonsToAdd = TaipyPlotlyButtons;
- // plConf.responsive = true; // this is the source of the on/off height ...
- plConf.autosizable = true;
- if (!active) {
- plConf.staticPlot = true;
- }
- return plConf;
- }, [active, props.plotConfig]);
- const onRelayout = useCallback(
- (eventData: PlotRelayoutEvent) => {
- onRangeChange && dispatch(createSendActionNameAction(id, module, { action: onRangeChange, ...eventData }));
- if (config.decimators && !config.types.includes("scatter3d")) {
- const [backCols, dtKeyBase] = getDataKey(config.columns, config.decimators);
- const dtKey = `${dtKeyBase}--${Object.entries(eventData)
- .map(([k, v]) => `${k}=${v}`)
- .join("-")}`;
- setDataKey(dtKey);
- dispatch(
- createRequestChartUpdateAction(
- updateVarName,
- id,
- module,
- backCols,
- dtKey,
- getDecimatorsPayload(
- config.decimators,
- plotRef.current,
- config.modes,
- config.columns,
- config.traces,
- eventData
- )
- )
- );
- }
- },
- [
- dispatch,
- onRangeChange,
- id,
- config.modes,
- config.columns,
- config.traces,
- config.types,
- config.decimators,
- updateVarName,
- module,
- ]
- );
- const clickHandler = useCallback(
- (evt?: MouseEvent) => {
- const map =
- (evt?.currentTarget as PlotlyDiv)?._fullLayout?.map ||
- (evt?.currentTarget as PlotlyDiv)?._fullLayout?.geo ||
- (evt?.currentTarget as PlotlyDiv)?._fullLayout?.mapbox;
- const xaxis = map ? map._subplot?.xaxis : (evt?.currentTarget as PlotlyDiv)?._fullLayout?.xaxis;
- const yaxis = map ? map._subplot?.xaxis : (evt?.currentTarget as PlotlyDiv)?._fullLayout?.yaxis;
- if (!xaxis || !yaxis) {
- console.info("clickHandler: Plotly div does not have an xaxis object", evt);
- return;
- }
- const transform = (axis: Axis, delta: keyof DOMRect) => {
- const bb = (evt?.target as HTMLDivElement).getBoundingClientRect();
- return (pos?: number) => axis.p2d((pos || 0) - (bb[delta] as number));
- };
- dispatch(
- createSendActionNameAction(
- id,
- module,
- lightenPayload({
- action: onClick,
- lat: map ? yaxis.p2c() : undefined,
- y: map ? undefined : transform(yaxis, "top")(evt?.clientY),
- lon: map ? xaxis.p2c() : undefined,
- x: map ? undefined : transform(xaxis, "left")(evt?.clientX),
- })
- )
- );
- },
- [dispatch, module, id, onClick]
- );
- const onInitialized = useCallback(
- (figure: Readonly<Figure>, graphDiv: Readonly<HTMLElement>) => {
- onClick && graphDiv.addEventListener("click", clickHandler);
- },
- [onClick, clickHandler]
- );
- const getRealIndex = useCallback(
- (index?: number) =>
- typeof index === "number"
- ? props.figure
- ? index
- : data[dataKey].tp_index
- ? (data[dataKey].tp_index[index] as number)
- : index
- : 0,
- [data, dataKey, props.figure]
- );
- const onSelect = useCallback(
- (evt?: PlotSelectionEvent) => {
- if (updateVars) {
- const traces = (evt?.points || []).reduce((tr, pt) => {
- tr[pt.curveNumber] = tr[pt.curveNumber] || [];
- tr[pt.curveNumber].push(getRealIndex(getPlotIndex(pt)));
- return tr;
- }, [] as number[][]);
- if (config.traces.length === 0) {
- // figure
- const theVar = getUpdateVar(updateVars, "selected");
- theVar && dispatch(createSendUpdateAction(theVar, traces, module, props.onChange, propagate));
- return;
- }
- if (traces.length) {
- const upvars = traces.map((_, idx) => getUpdateVar(updateVars, `selected${idx}`));
- const setVars = new Set(upvars.filter((v) => v));
- if (traces.length > 1 && setVars.size === 1) {
- dispatch(
- createSendUpdateAction(
- setVars.values().next().value,
- traces,
- module,
- props.onChange,
- propagate
- )
- );
- return;
- }
- traces.forEach((tr, idx) => {
- if (upvars[idx] && tr && tr.length) {
- dispatch(createSendUpdateAction(upvars[idx], tr, module, props.onChange, propagate));
- }
- });
- } else if (config.traces.length === 1) {
- const upVar = getUpdateVar(updateVars, "selected0");
- if (upVar) {
- dispatch(createSendUpdateAction(upVar, [], module, props.onChange, propagate));
- }
- }
- }
- },
- [getRealIndex, dispatch, updateVars, propagate, props.onChange, config.traces.length, module]
- );
- return render ? (
- <Tooltip title={hover || ""}>
- <Box id={id} className={`${className} ${getComponentClassName(props.children)}`} ref={plotRef}>
- <Suspense fallback={<Skeleton key="skeleton" sx={skelStyle} />}>
- {Array.isArray(props.figure) && props.figure.length && props.figure[0].data !== undefined ? (
- <Plot
- data={props.figure[0].data as Data[]}
- layout={layout}
- style={style}
- onRelayout={onRelayout}
- onSelected={onSelect}
- onDeselect={onSelect}
- config={plotConfig}
- useResizeHandler
- onInitialized={onInitialized}
- />
- ) : (
- <Plot
- data={dataPl}
- layout={layout}
- style={style}
- onRelayout={onRelayout}
- onSelected={isOnClick(config.types) ? undefined : onSelect}
- onDeselect={isOnClick(config.types) ? undefined : onSelect}
- onClick={isOnClick(config.types) ? onSelect : undefined}
- config={plotConfig}
- useResizeHandler
- onInitialized={onInitialized}
- />
- )}
- </Suspense>
- {props.children}
- </Box>
- </Tooltip>
- ) : null;
- };
- export default Chart;
|