123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990 |
- /*
- * 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,
- useCallback,
- useEffect,
- useMemo,
- useRef,
- useState,
- lazy,
- Suspense,
- } from "react";
- import {
- Config,
- Data,
- Layout,
- ModeBarButtonAny,
- PlotDatum,
- PlotMarker,
- PlotRelayoutEvent,
- PlotSelectionEvent,
- ScatterLine,
- } from "plotly.js";
- import Skeleton from "@mui/material/Skeleton";
- import Box from "@mui/material/Box";
- import Tooltip from "@mui/material/Tooltip";
- import { useTheme } from "@mui/material";
- import { getArrayValue, getUpdateVar, TaipyActiveProps, TaipyChangeProps } from "./utils";
- import {
- createRequestChartUpdateAction,
- createSendActionNameAction,
- createSendUpdateAction,
- } from "../../context/taipyReducers";
- import { ColumnDesc } from "./tableUtils";
- import {
- useClassNames,
- useDispatch,
- useDispatchRequestUpdateOnFirstRender,
- useDynamicJsonProperty,
- useDynamicProperty,
- useModule,
- } from "../../utils/hooks";
- 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;
- testId?: string;
- render?: boolean;
- defaultRender?: boolean;
- template?: string;
- template_Dark_?: string;
- template_Light_?: string;
- //[key: `selected_${number}`]: number[];
- figure?: Array<Record<string, unknown>>;
- }
- 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+)\/(.*)/;
- const getColNameFromIndexed = (colName: string): string => {
- if (colName) {
- const reRes = indexedData.exec(colName);
- if (reRes && reRes.length > 2) {
- return reRes[2] || colName;
- }
- }
- return colName;
- };
- 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;
- };
- const getValueFromCol = (values: TraceValueType | undefined, col: string): (string | number)[] => {
- if (values) {
- if (col) {
- 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 [];
- };
- 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],
- }
- : undefined
- ),
- 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 WithpointNumbers {
- pointNumbers: number[];
- }
- 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 Record<string, Record<string, unknown>>;
- const emptyData = {} as Record<string, TraceValueType>;
- 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 title = gd.classList.toggle("full-screen") ? "Exit Full screen" : "Full screen";
- (evt.currentTarget as HTMLElement).setAttribute("data-title", title);
- const {height} = gd.dataset;
- if (height) {
- gd.attributeStyleMap.set("height", height);
- } else {
- gd.setAttribute("data-height", getComputedStyle(gd).height)
- }
- window.dispatchEvent(new Event('resize'));
- },
- },
- ];
- const Chart = (props: ChartProp) => {
- const {
- title = "",
- width = "100%",
- height,
- updateVarName,
- updateVars,
- id,
- data = emptyData,
- onRangeChange,
- propagate = true,
- } = 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 = typeof data === "number" ? data : 0;
- 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 (e) {
- // too bad
- val = [];
- }
- }
- if (!Array.isArray(val)) {
- val = [];
- }
- if (
- idx >= sel.length ||
- val.length !== sel[idx].length ||
- val.some((v, i) => sel[idx][i] != v)
- ) {
- sel = sel.concat();
- sel[idx] = val;
- }
- }
- }
- });
- return sel;
- });
- }, [props]);
- const config = useDynamicJsonProperty(props.config, props.defaultConfig, defaultConfig);
- useEffect(() => {
- if (updateVarName && (refresh || !data[dataKey])) {
- const backCols = Object.values(config.columns).map((col) => col.dfid);
- const dtKey = backCols.join("-") + (config.decimators ? `--${config.decimators.join("")}` : "");
- 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_)
- : darkTemplate
- : 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 {
- ...(props.figure[0].layout as Partial<Layout>),
- ...layout,
- title: title || layout.title || (props.figure[0].layout as Partial<Layout>).title,
- clickmode: "event+select",
- } as Layout;
- }
- 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 (typeof data === "number" && lastDataPl.current) {
- 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 || {})};
- 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;
- }
- }
- });
- 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;
- })
- : [];
- 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.modeBarButtonsToAdd = TaipyPlotlyButtons;
- plconf.responsive = true;
- 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 = Object.values(config.columns).map((col) => col.dfid);
- const eventDataKey = Object.entries(eventData)
- .map(([k, v]) => `${k}=${v}`)
- .join("-");
- const dtKey =
- backCols.join("-") +
- (config.decimators ? `--${config.decimators.join("")}` : "") +
- "--" +
- eventDataKey;
- 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 onAfterPlot = useCallback(() => {
- // Manage loading Animation ... One day
- }, []);
- 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 (traces.length) {
- traces.forEach((tr, idx) => {
- const upvar = getUpdateVar(updateVars, `selected${idx}`);
- if (upvar && tr && tr.length) {
- dispatch(createSendUpdateAction(upvar, 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 ? (
- <Box id={id} data-testid={props.testId} className={className} ref={plotRef}>
- <Tooltip title={hover || ""}>
- <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}
- onAfterPlot={onAfterPlot}
- onSelected={onSelect}
- onDeselect={onSelect}
- config={plotConfig}
- useResizeHandler
- />
- ) : (
- <Plot
- data={dataPl}
- layout={layout}
- style={style}
- onRelayout={onRelayout}
- onAfterPlot={onAfterPlot}
- onSelected={isOnClick(config.types) ? undefined : onSelect}
- onDeselect={isOnClick(config.types) ? undefined : onSelect}
- onClick={isOnClick(config.types) ? onSelect : undefined}
- config={plotConfig}
- useResizeHandler
- />
- )}
- </Suspense>
- </Tooltip>
- </Box>
- ) : null;
- };
- export default Chart;
- const darkTemplate = {
- data: {
- barpolar: [
- {
- marker: {
- line: {
- color: "rgb(17,17,17)",
- },
- pattern: {
- solidity: 0.2,
- },
- },
- type: "barpolar",
- },
- ],
- bar: [
- {
- error_x: {
- color: "#f2f5fa",
- },
- error_y: {
- color: "#f2f5fa",
- },
- marker: {
- line: {
- color: "rgb(17,17,17)",
- },
- pattern: {
- solidity: 0.2,
- },
- },
- type: "bar",
- },
- ],
- carpet: [
- {
- aaxis: {
- endlinecolor: "#A2B1C6",
- gridcolor: "#506784",
- linecolor: "#506784",
- minorgridcolor: "#506784",
- startlinecolor: "#A2B1C6",
- },
- baxis: {
- endlinecolor: "#A2B1C6",
- gridcolor: "#506784",
- linecolor: "#506784",
- minorgridcolor: "#506784",
- startlinecolor: "#A2B1C6",
- },
- type: "carpet",
- },
- ],
- contour: [
- {
- colorscale: [
- [0.0, "#0d0887"],
- [0.1111111111111111, "#46039f"],
- [0.2222222222222222, "#7201a8"],
- [0.3333333333333333, "#9c179e"],
- [0.4444444444444444, "#bd3786"],
- [0.5555555555555556, "#d8576b"],
- [0.6666666666666666, "#ed7953"],
- [0.7777777777777778, "#fb9f3a"],
- [0.8888888888888888, "#fdca26"],
- [1.0, "#f0f921"],
- ],
- type: "contour",
- },
- ],
- heatmapgl: [
- {
- colorscale: [
- [0.0, "#0d0887"],
- [0.1111111111111111, "#46039f"],
- [0.2222222222222222, "#7201a8"],
- [0.3333333333333333, "#9c179e"],
- [0.4444444444444444, "#bd3786"],
- [0.5555555555555556, "#d8576b"],
- [0.6666666666666666, "#ed7953"],
- [0.7777777777777778, "#fb9f3a"],
- [0.8888888888888888, "#fdca26"],
- [1.0, "#f0f921"],
- ],
- type: "heatmapgl",
- },
- ],
- heatmap: [
- {
- colorscale: [
- [0.0, "#0d0887"],
- [0.1111111111111111, "#46039f"],
- [0.2222222222222222, "#7201a8"],
- [0.3333333333333333, "#9c179e"],
- [0.4444444444444444, "#bd3786"],
- [0.5555555555555556, "#d8576b"],
- [0.6666666666666666, "#ed7953"],
- [0.7777777777777778, "#fb9f3a"],
- [0.8888888888888888, "#fdca26"],
- [1.0, "#f0f921"],
- ],
- type: "heatmap",
- },
- ],
- histogram2dcontour: [
- {
- colorscale: [
- [0.0, "#0d0887"],
- [0.1111111111111111, "#46039f"],
- [0.2222222222222222, "#7201a8"],
- [0.3333333333333333, "#9c179e"],
- [0.4444444444444444, "#bd3786"],
- [0.5555555555555556, "#d8576b"],
- [0.6666666666666666, "#ed7953"],
- [0.7777777777777778, "#fb9f3a"],
- [0.8888888888888888, "#fdca26"],
- [1.0, "#f0f921"],
- ],
- type: "histogram2dcontour",
- },
- ],
- histogram2d: [
- {
- colorscale: [
- [0.0, "#0d0887"],
- [0.1111111111111111, "#46039f"],
- [0.2222222222222222, "#7201a8"],
- [0.3333333333333333, "#9c179e"],
- [0.4444444444444444, "#bd3786"],
- [0.5555555555555556, "#d8576b"],
- [0.6666666666666666, "#ed7953"],
- [0.7777777777777778, "#fb9f3a"],
- [0.8888888888888888, "#fdca26"],
- [1.0, "#f0f921"],
- ],
- type: "histogram2d",
- },
- ],
- histogram: [
- {
- marker: {
- pattern: {
- solidity: 0.2,
- },
- },
- type: "histogram",
- },
- ],
- scatter: [
- {
- marker: {
- line: {
- color: "#283442",
- },
- },
- type: "scatter",
- },
- ],
- scattergl: [
- {
- marker: {
- line: {
- color: "#283442",
- },
- },
- type: "scattergl",
- },
- ],
- surface: [
- {
- colorscale: [
- [0.0, "#0d0887"],
- [0.1111111111111111, "#46039f"],
- [0.2222222222222222, "#7201a8"],
- [0.3333333333333333, "#9c179e"],
- [0.4444444444444444, "#bd3786"],
- [0.5555555555555556, "#d8576b"],
- [0.6666666666666666, "#ed7953"],
- [0.7777777777777778, "#fb9f3a"],
- [0.8888888888888888, "#fdca26"],
- [1.0, "#f0f921"],
- ],
- type: "surface",
- },
- ],
- table: [
- {
- cells: {
- fill: {
- color: "#506784",
- },
- line: {
- color: "rgb(17,17,17)",
- },
- },
- header: {
- fill: {
- color: "#2a3f5f",
- },
- line: {
- color: "rgb(17,17,17)",
- },
- },
- type: "table",
- },
- ],
- },
- layout: {
- annotationdefaults: {
- arrowcolor: "#f2f5fa",
- },
- colorscale: {
- diverging: [
- [0, "#8e0152"],
- [0.1, "#c51b7d"],
- [0.2, "#de77ae"],
- [0.3, "#f1b6da"],
- [0.4, "#fde0ef"],
- [0.5, "#f7f7f7"],
- [0.6, "#e6f5d0"],
- [0.7, "#b8e186"],
- [0.8, "#7fbc41"],
- [0.9, "#4d9221"],
- [1, "#276419"],
- ],
- sequential: [
- [0.0, "#0d0887"],
- [0.1111111111111111, "#46039f"],
- [0.2222222222222222, "#7201a8"],
- [0.3333333333333333, "#9c179e"],
- [0.4444444444444444, "#bd3786"],
- [0.5555555555555556, "#d8576b"],
- [0.6666666666666666, "#ed7953"],
- [0.7777777777777778, "#fb9f3a"],
- [0.8888888888888888, "#fdca26"],
- [1.0, "#f0f921"],
- ],
- sequentialminus: [
- [0.0, "#0d0887"],
- [0.1111111111111111, "#46039f"],
- [0.2222222222222222, "#7201a8"],
- [0.3333333333333333, "#9c179e"],
- [0.4444444444444444, "#bd3786"],
- [0.5555555555555556, "#d8576b"],
- [0.6666666666666666, "#ed7953"],
- [0.7777777777777778, "#fb9f3a"],
- [0.8888888888888888, "#fdca26"],
- [1.0, "#f0f921"],
- ],
- },
- colorway: [
- "#636efa",
- "#EF553B",
- "#00cc96",
- "#ab63fa",
- "#FFA15A",
- "#19d3f3",
- "#FF6692",
- "#B6E880",
- "#FF97FF",
- "#FECB52",
- ],
- font: {
- color: "#f2f5fa",
- },
- geo: {
- bgcolor: "rgb(17,17,17)",
- lakecolor: "rgb(17,17,17)",
- landcolor: "rgb(17,17,17)",
- subunitcolor: "#506784",
- },
- mapbox: {
- style: "dark",
- },
- paper_bgcolor: "rgb(17,17,17)",
- plot_bgcolor: "rgb(17,17,17)",
- polar: {
- angularaxis: {
- gridcolor: "#506784",
- linecolor: "#506784",
- },
- bgcolor: "rgb(17,17,17)",
- radialaxis: {
- gridcolor: "#506784",
- linecolor: "#506784",
- },
- },
- scene: {
- xaxis: {
- backgroundcolor: "rgb(17,17,17)",
- gridcolor: "#506784",
- linecolor: "#506784",
- zerolinecolor: "#C8D4E3",
- },
- yaxis: {
- backgroundcolor: "rgb(17,17,17)",
- gridcolor: "#506784",
- linecolor: "#506784",
- zerolinecolor: "#C8D4E3",
- },
- zaxis: {
- backgroundcolor: "rgb(17,17,17)",
- gridcolor: "#506784",
- linecolor: "#506784",
- showbackground: true,
- zerolinecolor: "#C8D4E3",
- },
- },
- shapedefaults: {
- line: {
- color: "#f2f5fa",
- },
- },
- sliderdefaults: {
- bgcolor: "#C8D4E3",
- bordercolor: "rgb(17,17,17)",
- },
- ternary: {
- aaxis: {
- gridcolor: "#506784",
- linecolor: "#506784",
- },
- baxis: {
- gridcolor: "#506784",
- linecolor: "#506784",
- },
- bgcolor: "rgb(17,17,17)",
- caxis: {
- gridcolor: "#506784",
- linecolor: "#506784",
- },
- },
- updatemenudefaults: {
- bgcolor: "#506784",
- },
- xaxis: {
- gridcolor: "#283442",
- linecolor: "#506784",
- tickcolor: "#506784",
- zerolinecolor: "#283442",
- },
- yaxis: {
- gridcolor: "#283442",
- linecolor: "#506784",
- tickcolor: "#506784",
- zerolinecolor: "#283442",
- },
- },
- };
|