Chart.tsx 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990
  1. /*
  2. * Copyright 2021-2024 Avaiga Private Limited
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
  5. * the License. You may obtain a copy of the License at
  6. *
  7. * http://www.apache.org/licenses/LICENSE-2.0
  8. *
  9. * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
  10. * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
  11. * specific language governing permissions and limitations under the License.
  12. */
  13. import React, {
  14. CSSProperties,
  15. useCallback,
  16. useEffect,
  17. useMemo,
  18. useRef,
  19. useState,
  20. lazy,
  21. Suspense,
  22. } from "react";
  23. import {
  24. Config,
  25. Data,
  26. Layout,
  27. ModeBarButtonAny,
  28. PlotDatum,
  29. PlotMarker,
  30. PlotRelayoutEvent,
  31. PlotSelectionEvent,
  32. ScatterLine,
  33. } from "plotly.js";
  34. import Skeleton from "@mui/material/Skeleton";
  35. import Box from "@mui/material/Box";
  36. import Tooltip from "@mui/material/Tooltip";
  37. import { useTheme } from "@mui/material";
  38. import { getArrayValue, getUpdateVar, TaipyActiveProps, TaipyChangeProps } from "./utils";
  39. import {
  40. createRequestChartUpdateAction,
  41. createSendActionNameAction,
  42. createSendUpdateAction,
  43. } from "../../context/taipyReducers";
  44. import { ColumnDesc } from "./tableUtils";
  45. import {
  46. useClassNames,
  47. useDispatch,
  48. useDispatchRequestUpdateOnFirstRender,
  49. useDynamicJsonProperty,
  50. useDynamicProperty,
  51. useModule,
  52. } from "../../utils/hooks";
  53. const Plot = lazy(() => import("react-plotly.js"));
  54. interface ChartProp extends TaipyActiveProps, TaipyChangeProps {
  55. title?: string;
  56. width?: string | number;
  57. height?: string | number;
  58. defaultConfig: string;
  59. config?: string;
  60. data?: Record<string, TraceValueType>;
  61. defaultLayout?: string;
  62. layout?: string;
  63. plotConfig?: string;
  64. onRangeChange?: string;
  65. testId?: string;
  66. render?: boolean;
  67. defaultRender?: boolean;
  68. template?: string;
  69. template_Dark_?: string;
  70. template_Light_?: string;
  71. //[key: `selected_${number}`]: number[];
  72. figure?: Array<Record<string, unknown>>;
  73. }
  74. interface ChartConfig {
  75. columns: Record<string, ColumnDesc>;
  76. labels: string[];
  77. modes: string[];
  78. types: string[];
  79. traces: string[][];
  80. xaxis: string[];
  81. yaxis: string[];
  82. markers: Partial<PlotMarker>[];
  83. selectedMarkers: Partial<PlotMarker>[];
  84. orientations: string[];
  85. names: string[];
  86. lines: Partial<ScatterLine>[];
  87. texts: string[];
  88. textAnchors: string[];
  89. options: Record<string, unknown>[];
  90. axisNames: Array<string[]>;
  91. addIndex: Array<boolean>;
  92. decimators?: string[];
  93. }
  94. export type TraceValueType = Record<string, (string | number)[]>;
  95. const defaultStyle = { position: "relative", display: "inline-block" };
  96. const indexedData = /^(\d+)\/(.*)/;
  97. const getColNameFromIndexed = (colName: string): string => {
  98. if (colName) {
  99. const reRes = indexedData.exec(colName);
  100. if (reRes && reRes.length > 2) {
  101. return reRes[2] || colName;
  102. }
  103. }
  104. return colName;
  105. };
  106. const getValue = <T,>(
  107. values: TraceValueType | undefined,
  108. arr: T[],
  109. idx: number,
  110. returnUndefined = false
  111. ): (string | number)[] | undefined => {
  112. const value = getValueFromCol(values, getArrayValue(arr, idx) as unknown as string);
  113. if (!returnUndefined || value.length) {
  114. return value;
  115. }
  116. return undefined;
  117. };
  118. const getValueFromCol = (values: TraceValueType | undefined, col: string): (string | number)[] => {
  119. if (values) {
  120. if (col) {
  121. if (Array.isArray(values)) {
  122. const reRes = indexedData.exec(col);
  123. if (reRes && reRes.length > 2) {
  124. return values[parseInt(reRes[1], 10) || 0][reRes[2] || col] || [];
  125. }
  126. } else {
  127. return values[col] || [];
  128. }
  129. }
  130. }
  131. return [];
  132. };
  133. const getAxis = (traces: string[][], idx: number, columns: Record<string, ColumnDesc>, axis: number) => {
  134. if (traces.length > idx && traces[idx].length > axis && traces[idx][axis] && columns[traces[idx][axis]])
  135. return columns[traces[idx][axis]].dfid;
  136. return undefined;
  137. };
  138. const getDecimatorsPayload = (
  139. decimators: string[] | undefined,
  140. plotDiv: HTMLDivElement | null,
  141. modes: string[],
  142. columns: Record<string, ColumnDesc>,
  143. traces: string[][],
  144. relayoutData?: PlotRelayoutEvent
  145. ) => {
  146. return decimators
  147. ? {
  148. width: plotDiv?.clientWidth,
  149. height: plotDiv?.clientHeight,
  150. decimators: decimators.map((d, i) =>
  151. d
  152. ? {
  153. decimator: d,
  154. xAxis: getAxis(traces, i, columns, 0),
  155. yAxis: getAxis(traces, i, columns, 1),
  156. zAxis: getAxis(traces, i, columns, 2),
  157. chartMode: modes[i],
  158. }
  159. : undefined
  160. ),
  161. relayoutData: relayoutData,
  162. }
  163. : undefined;
  164. };
  165. const selectedPropRe = /selected(\d+)/;
  166. const MARKER_TO_COL = ["color", "size", "symbol", "opacity", "colors"];
  167. const isOnClick = (types: string[]) => (types?.length ? types.every((t) => t === "pie") : false);
  168. interface WithpointNumbers {
  169. pointNumbers: number[];
  170. }
  171. const getPlotIndex = (pt: PlotDatum) =>
  172. pt.pointIndex === undefined
  173. ? pt.pointNumber === undefined
  174. ? (pt as unknown as WithpointNumbers).pointNumbers?.length
  175. ? (pt as unknown as WithpointNumbers).pointNumbers[0]
  176. : 0
  177. : pt.pointNumber
  178. : pt.pointIndex;
  179. const defaultConfig = {
  180. columns: {} as Record<string, ColumnDesc>,
  181. labels: [],
  182. modes: [],
  183. types: [],
  184. traces: [],
  185. xaxis: [],
  186. yaxis: [],
  187. markers: [],
  188. selectedMarkers: [],
  189. orientations: [],
  190. names: [],
  191. lines: [],
  192. texts: [],
  193. textAnchors: [],
  194. options: [],
  195. axisNames: [],
  196. addIndex: [],
  197. } as ChartConfig;
  198. const emptyLayout = {} as Record<string, Record<string, unknown>>;
  199. const emptyData = {} as Record<string, TraceValueType>;
  200. const TaipyPlotlyButtons: ModeBarButtonAny[] = [
  201. {
  202. name: "Full screen",
  203. title: "Full screen",
  204. icon: {
  205. height: 24,
  206. width: 24,
  207. path: "M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z",
  208. },
  209. click: function (gd: HTMLElement, evt: Event) {
  210. const title = gd.classList.toggle("full-screen") ? "Exit Full screen" : "Full screen";
  211. (evt.currentTarget as HTMLElement).setAttribute("data-title", title);
  212. const {height} = gd.dataset;
  213. if (height) {
  214. gd.attributeStyleMap.set("height", height);
  215. } else {
  216. gd.setAttribute("data-height", getComputedStyle(gd).height)
  217. }
  218. window.dispatchEvent(new Event('resize'));
  219. },
  220. },
  221. ];
  222. const Chart = (props: ChartProp) => {
  223. const {
  224. title = "",
  225. width = "100%",
  226. height,
  227. updateVarName,
  228. updateVars,
  229. id,
  230. data = emptyData,
  231. onRangeChange,
  232. propagate = true,
  233. } = props;
  234. const dispatch = useDispatch();
  235. const [selected, setSelected] = useState<number[][]>([]);
  236. const plotRef = useRef<HTMLDivElement>(null);
  237. const [dataKey, setDataKey] = useState("__default__");
  238. const lastDataPl = useRef<Data[]>([]);
  239. const theme = useTheme();
  240. const module = useModule();
  241. const refresh = typeof data === "number" ? data : 0;
  242. const className = useClassNames(props.libClassName, props.dynamicClassName, props.className);
  243. const active = useDynamicProperty(props.active, props.defaultActive, true);
  244. const render = useDynamicProperty(props.render, props.defaultRender, true);
  245. const hover = useDynamicProperty(props.hoverText, props.defaultHoverText, undefined);
  246. const baseLayout = useDynamicJsonProperty(props.layout, props.defaultLayout || "", emptyLayout);
  247. // get props.selected[i] values
  248. useEffect(() => {
  249. if (props.figure) {
  250. return;
  251. }
  252. setSelected((sel) => {
  253. Object.keys(props).forEach((key) => {
  254. const res = selectedPropRe.exec(key);
  255. if (res && res.length == 2) {
  256. const idx = parseInt(res[1], 10);
  257. let val = (props as unknown as Record<string, number[]>)[key];
  258. if (val !== undefined) {
  259. if (typeof val === "string") {
  260. try {
  261. val = JSON.parse(val) as number[];
  262. } catch (e) {
  263. // too bad
  264. val = [];
  265. }
  266. }
  267. if (!Array.isArray(val)) {
  268. val = [];
  269. }
  270. if (
  271. idx >= sel.length ||
  272. val.length !== sel[idx].length ||
  273. val.some((v, i) => sel[idx][i] != v)
  274. ) {
  275. sel = sel.concat();
  276. sel[idx] = val;
  277. }
  278. }
  279. }
  280. });
  281. return sel;
  282. });
  283. }, [props]);
  284. const config = useDynamicJsonProperty(props.config, props.defaultConfig, defaultConfig);
  285. useEffect(() => {
  286. if (updateVarName && (refresh || !data[dataKey])) {
  287. const backCols = Object.values(config.columns).map((col) => col.dfid);
  288. const dtKey = backCols.join("-") + (config.decimators ? `--${config.decimators.join("")}` : "");
  289. setDataKey(dtKey);
  290. if (refresh || !data[dtKey]) {
  291. dispatch(
  292. createRequestChartUpdateAction(
  293. updateVarName,
  294. id,
  295. module,
  296. backCols,
  297. dtKey,
  298. getDecimatorsPayload(
  299. config.decimators,
  300. plotRef.current,
  301. config.modes,
  302. config.columns,
  303. config.traces
  304. )
  305. )
  306. );
  307. }
  308. }
  309. // eslint-disable-next-line react-hooks/exhaustive-deps
  310. }, [refresh, dispatch, config.columns, config.traces, config.modes, config.decimators, updateVarName, id, module]);
  311. useDispatchRequestUpdateOnFirstRender(dispatch, id, module, updateVars);
  312. const layout = useMemo(() => {
  313. const layout = { ...baseLayout };
  314. let template = undefined;
  315. try {
  316. const tpl = props.template && JSON.parse(props.template);
  317. const tplTheme =
  318. theme.palette.mode === "dark"
  319. ? props.template_Dark_
  320. ? JSON.parse(props.template_Dark_)
  321. : darkTemplate
  322. : props.template_Light_ && JSON.parse(props.template_Light_);
  323. template = tpl ? (tplTheme ? { ...tpl, ...tplTheme } : tpl) : tplTheme ? tplTheme : undefined;
  324. } catch (e) {
  325. console.info(`Error while parsing Chart.template\n${(e as Error).message || e}`);
  326. }
  327. if (template) {
  328. layout.template = template;
  329. }
  330. if (props.figure) {
  331. return {
  332. ...(props.figure[0].layout as Partial<Layout>),
  333. ...layout,
  334. title: title || layout.title || (props.figure[0].layout as Partial<Layout>).title,
  335. clickmode: "event+select",
  336. } as Layout;
  337. }
  338. return {
  339. ...layout,
  340. autosize: true,
  341. title: title || layout.title,
  342. xaxis: {
  343. title:
  344. config.traces.length && config.traces[0].length && config.traces[0][0]
  345. ? getColNameFromIndexed(config.columns[config.traces[0][0]]?.dfid)
  346. : undefined,
  347. ...layout.xaxis,
  348. },
  349. yaxis: {
  350. title:
  351. config.traces.length == 1 && config.traces[0].length > 1 && config.columns[config.traces[0][1]]
  352. ? getColNameFromIndexed(config.columns[config.traces[0][1]]?.dfid)
  353. : undefined,
  354. ...layout.yaxis,
  355. },
  356. clickmode: "event+select",
  357. } as Layout;
  358. }, [
  359. theme.palette.mode,
  360. title,
  361. config.columns,
  362. config.traces,
  363. baseLayout,
  364. props.template,
  365. props.template_Dark_,
  366. props.template_Light_,
  367. props.figure,
  368. ]);
  369. const style = useMemo(
  370. () =>
  371. height === undefined
  372. ? ({ ...defaultStyle, width: width } as CSSProperties)
  373. : ({ ...defaultStyle, width: width, height: height } as CSSProperties),
  374. [width, height]
  375. );
  376. const skelStyle = useMemo(() => ({ ...style, minHeight: "7em" }), [style]);
  377. const dataPl = useMemo(() => {
  378. if (props.figure) {
  379. return lastDataPl.current;
  380. }
  381. if (typeof data === "number" && lastDataPl.current) {
  382. return lastDataPl.current;
  383. }
  384. const datum = data[dataKey];
  385. lastDataPl.current = datum
  386. ? config.traces.map((trace, idx) => {
  387. const ret = {
  388. ...getArrayValue(config.options, idx, {}),
  389. type: config.types[idx],
  390. mode: config.modes[idx],
  391. name:
  392. getArrayValue(config.names, idx) ||
  393. (config.columns[trace[1]] ? getColNameFromIndexed(config.columns[trace[1]].dfid) : undefined),
  394. } as Record<string, unknown>;
  395. ret.marker = {...getArrayValue(config.markers, idx, ret.marker || {})};
  396. MARKER_TO_COL.forEach((prop) => {
  397. const val = (ret.marker as Record<string, unknown>)[prop];
  398. if (typeof val === "string") {
  399. const arr = getValueFromCol(datum, val as string);
  400. if (arr.length) {
  401. (ret.marker as Record<string, unknown>)[prop] = arr;
  402. }
  403. }
  404. });
  405. const xs = getValue(datum, trace, 0) || [];
  406. const ys = getValue(datum, trace, 1) || [];
  407. const addIndex = getArrayValue(config.addIndex, idx, true) && !ys.length;
  408. const baseX = addIndex ? Array.from(Array(xs.length).keys()) : xs;
  409. const baseY = addIndex ? xs : ys;
  410. const axisNames = config.axisNames.length > idx ? config.axisNames[idx] : ([] as string[]);
  411. if (baseX.length) {
  412. if (axisNames.length > 0) {
  413. ret[axisNames[0]] = baseX;
  414. } else {
  415. ret.x = baseX;
  416. }
  417. }
  418. if (baseY.length) {
  419. if (axisNames.length > 1) {
  420. ret[axisNames[1]] = baseY;
  421. } else {
  422. ret.y = baseY;
  423. }
  424. }
  425. const baseZ = getValue(datum, trace, 2, true);
  426. if (baseZ) {
  427. if (axisNames.length > 2) {
  428. ret[axisNames[2]] = baseZ;
  429. } else {
  430. ret.z = baseZ;
  431. }
  432. }
  433. // Hack for treemap charts: create a fallback 'parents' column if needed
  434. // This works ONLY because 'parents' is the third named axis
  435. // (see __CHART_AXIS in gui/utils/chart_config_builder.py)
  436. else if (config.types[idx] === "treemap" && Array.isArray(ret.labels)) {
  437. ret.parents = Array(ret.labels.length).fill("");
  438. }
  439. // Other axis
  440. for (let i = 3; i < axisNames.length; i++) {
  441. ret[axisNames[i]] = getValue(datum, trace, i, true);
  442. }
  443. ret.text = getValue(datum, config.texts, idx, true);
  444. ret.xaxis = config.xaxis[idx];
  445. ret.yaxis = config.yaxis[idx];
  446. ret.hovertext = getValue(datum, config.labels, idx, true);
  447. const selPoints = getArrayValue(selected, idx, []);
  448. if (selPoints?.length) {
  449. ret.selectedpoints = selPoints;
  450. }
  451. ret.orientation = getArrayValue(config.orientations, idx);
  452. ret.line = getArrayValue(config.lines, idx);
  453. ret.textposition = getArrayValue(config.textAnchors, idx);
  454. const selectedMarker = getArrayValue(config.selectedMarkers, idx);
  455. if (selectedMarker) {
  456. ret.selected = { marker: selectedMarker };
  457. }
  458. return ret as Data;
  459. })
  460. : [];
  461. return lastDataPl.current;
  462. }, [props.figure, selected, data, config, dataKey]);
  463. const plotConfig = useMemo(() => {
  464. let plconf: Partial<Config> = {};
  465. if (props.plotConfig) {
  466. try {
  467. plconf = JSON.parse(props.plotConfig);
  468. } catch (e) {
  469. console.info(`Error while parsing Chart.plot_config\n${(e as Error).message || e}`);
  470. }
  471. if (typeof plconf !== "object" || plconf === null || Array.isArray(plconf)) {
  472. console.info("Error Chart.plot_config is not a dictionary");
  473. plconf = {};
  474. }
  475. }
  476. plconf.modeBarButtonsToAdd = TaipyPlotlyButtons;
  477. plconf.responsive = true;
  478. plconf.autosizable = true;
  479. if (!active) {
  480. plconf.staticPlot = true;
  481. }
  482. return plconf;
  483. }, [active, props.plotConfig]);
  484. const onRelayout = useCallback(
  485. (eventData: PlotRelayoutEvent) => {
  486. onRangeChange && dispatch(createSendActionNameAction(id, module, { action: onRangeChange, ...eventData }));
  487. if (config.decimators && !config.types.includes("scatter3d")) {
  488. const backCols = Object.values(config.columns).map((col) => col.dfid);
  489. const eventDataKey = Object.entries(eventData)
  490. .map(([k, v]) => `${k}=${v}`)
  491. .join("-");
  492. const dtKey =
  493. backCols.join("-") +
  494. (config.decimators ? `--${config.decimators.join("")}` : "") +
  495. "--" +
  496. eventDataKey;
  497. setDataKey(dtKey);
  498. dispatch(
  499. createRequestChartUpdateAction(
  500. updateVarName,
  501. id,
  502. module,
  503. backCols,
  504. dtKey,
  505. getDecimatorsPayload(
  506. config.decimators,
  507. plotRef.current,
  508. config.modes,
  509. config.columns,
  510. config.traces,
  511. eventData
  512. )
  513. )
  514. );
  515. }
  516. },
  517. [
  518. dispatch,
  519. onRangeChange,
  520. id,
  521. config.modes,
  522. config.columns,
  523. config.traces,
  524. config.types,
  525. config.decimators,
  526. updateVarName,
  527. module,
  528. ]
  529. );
  530. const onAfterPlot = useCallback(() => {
  531. // Manage loading Animation ... One day
  532. }, []);
  533. const getRealIndex = useCallback(
  534. (index?: number) =>
  535. typeof index === "number"
  536. ? props.figure
  537. ? index
  538. : data[dataKey].tp_index
  539. ? (data[dataKey].tp_index[index] as number)
  540. : index
  541. : 0,
  542. [data, dataKey, props.figure]
  543. );
  544. const onSelect = useCallback(
  545. (evt?: PlotSelectionEvent) => {
  546. if (updateVars) {
  547. const traces = (evt?.points || []).reduce((tr, pt) => {
  548. tr[pt.curveNumber] = tr[pt.curveNumber] || [];
  549. tr[pt.curveNumber].push(getRealIndex(getPlotIndex(pt)));
  550. return tr;
  551. }, [] as number[][]);
  552. if (traces.length) {
  553. traces.forEach((tr, idx) => {
  554. const upvar = getUpdateVar(updateVars, `selected${idx}`);
  555. if (upvar && tr && tr.length) {
  556. dispatch(createSendUpdateAction(upvar, tr, module, props.onChange, propagate));
  557. }
  558. });
  559. } else if (config.traces.length === 1) {
  560. const upvar = getUpdateVar(updateVars, "selected0");
  561. if (upvar) {
  562. dispatch(createSendUpdateAction(upvar, [], module, props.onChange, propagate));
  563. }
  564. }
  565. }
  566. },
  567. [getRealIndex, dispatch, updateVars, propagate, props.onChange, config.traces.length, module]
  568. );
  569. return render ? (
  570. <Box id={id} data-testid={props.testId} className={className} ref={plotRef}>
  571. <Tooltip title={hover || ""}>
  572. <Suspense fallback={<Skeleton key="skeleton" sx={skelStyle} />}>
  573. {Array.isArray(props.figure) && props.figure.length && props.figure[0].data !== undefined ? (
  574. <Plot
  575. data={props.figure[0].data as Data[]}
  576. layout={layout}
  577. style={style}
  578. onRelayout={onRelayout}
  579. onAfterPlot={onAfterPlot}
  580. onSelected={onSelect}
  581. onDeselect={onSelect}
  582. config={plotConfig}
  583. useResizeHandler
  584. />
  585. ) : (
  586. <Plot
  587. data={dataPl}
  588. layout={layout}
  589. style={style}
  590. onRelayout={onRelayout}
  591. onAfterPlot={onAfterPlot}
  592. onSelected={isOnClick(config.types) ? undefined : onSelect}
  593. onDeselect={isOnClick(config.types) ? undefined : onSelect}
  594. onClick={isOnClick(config.types) ? onSelect : undefined}
  595. config={plotConfig}
  596. useResizeHandler
  597. />
  598. )}
  599. </Suspense>
  600. </Tooltip>
  601. </Box>
  602. ) : null;
  603. };
  604. export default Chart;
  605. const darkTemplate = {
  606. data: {
  607. barpolar: [
  608. {
  609. marker: {
  610. line: {
  611. color: "rgb(17,17,17)",
  612. },
  613. pattern: {
  614. solidity: 0.2,
  615. },
  616. },
  617. type: "barpolar",
  618. },
  619. ],
  620. bar: [
  621. {
  622. error_x: {
  623. color: "#f2f5fa",
  624. },
  625. error_y: {
  626. color: "#f2f5fa",
  627. },
  628. marker: {
  629. line: {
  630. color: "rgb(17,17,17)",
  631. },
  632. pattern: {
  633. solidity: 0.2,
  634. },
  635. },
  636. type: "bar",
  637. },
  638. ],
  639. carpet: [
  640. {
  641. aaxis: {
  642. endlinecolor: "#A2B1C6",
  643. gridcolor: "#506784",
  644. linecolor: "#506784",
  645. minorgridcolor: "#506784",
  646. startlinecolor: "#A2B1C6",
  647. },
  648. baxis: {
  649. endlinecolor: "#A2B1C6",
  650. gridcolor: "#506784",
  651. linecolor: "#506784",
  652. minorgridcolor: "#506784",
  653. startlinecolor: "#A2B1C6",
  654. },
  655. type: "carpet",
  656. },
  657. ],
  658. contour: [
  659. {
  660. colorscale: [
  661. [0.0, "#0d0887"],
  662. [0.1111111111111111, "#46039f"],
  663. [0.2222222222222222, "#7201a8"],
  664. [0.3333333333333333, "#9c179e"],
  665. [0.4444444444444444, "#bd3786"],
  666. [0.5555555555555556, "#d8576b"],
  667. [0.6666666666666666, "#ed7953"],
  668. [0.7777777777777778, "#fb9f3a"],
  669. [0.8888888888888888, "#fdca26"],
  670. [1.0, "#f0f921"],
  671. ],
  672. type: "contour",
  673. },
  674. ],
  675. heatmapgl: [
  676. {
  677. colorscale: [
  678. [0.0, "#0d0887"],
  679. [0.1111111111111111, "#46039f"],
  680. [0.2222222222222222, "#7201a8"],
  681. [0.3333333333333333, "#9c179e"],
  682. [0.4444444444444444, "#bd3786"],
  683. [0.5555555555555556, "#d8576b"],
  684. [0.6666666666666666, "#ed7953"],
  685. [0.7777777777777778, "#fb9f3a"],
  686. [0.8888888888888888, "#fdca26"],
  687. [1.0, "#f0f921"],
  688. ],
  689. type: "heatmapgl",
  690. },
  691. ],
  692. heatmap: [
  693. {
  694. colorscale: [
  695. [0.0, "#0d0887"],
  696. [0.1111111111111111, "#46039f"],
  697. [0.2222222222222222, "#7201a8"],
  698. [0.3333333333333333, "#9c179e"],
  699. [0.4444444444444444, "#bd3786"],
  700. [0.5555555555555556, "#d8576b"],
  701. [0.6666666666666666, "#ed7953"],
  702. [0.7777777777777778, "#fb9f3a"],
  703. [0.8888888888888888, "#fdca26"],
  704. [1.0, "#f0f921"],
  705. ],
  706. type: "heatmap",
  707. },
  708. ],
  709. histogram2dcontour: [
  710. {
  711. colorscale: [
  712. [0.0, "#0d0887"],
  713. [0.1111111111111111, "#46039f"],
  714. [0.2222222222222222, "#7201a8"],
  715. [0.3333333333333333, "#9c179e"],
  716. [0.4444444444444444, "#bd3786"],
  717. [0.5555555555555556, "#d8576b"],
  718. [0.6666666666666666, "#ed7953"],
  719. [0.7777777777777778, "#fb9f3a"],
  720. [0.8888888888888888, "#fdca26"],
  721. [1.0, "#f0f921"],
  722. ],
  723. type: "histogram2dcontour",
  724. },
  725. ],
  726. histogram2d: [
  727. {
  728. colorscale: [
  729. [0.0, "#0d0887"],
  730. [0.1111111111111111, "#46039f"],
  731. [0.2222222222222222, "#7201a8"],
  732. [0.3333333333333333, "#9c179e"],
  733. [0.4444444444444444, "#bd3786"],
  734. [0.5555555555555556, "#d8576b"],
  735. [0.6666666666666666, "#ed7953"],
  736. [0.7777777777777778, "#fb9f3a"],
  737. [0.8888888888888888, "#fdca26"],
  738. [1.0, "#f0f921"],
  739. ],
  740. type: "histogram2d",
  741. },
  742. ],
  743. histogram: [
  744. {
  745. marker: {
  746. pattern: {
  747. solidity: 0.2,
  748. },
  749. },
  750. type: "histogram",
  751. },
  752. ],
  753. scatter: [
  754. {
  755. marker: {
  756. line: {
  757. color: "#283442",
  758. },
  759. },
  760. type: "scatter",
  761. },
  762. ],
  763. scattergl: [
  764. {
  765. marker: {
  766. line: {
  767. color: "#283442",
  768. },
  769. },
  770. type: "scattergl",
  771. },
  772. ],
  773. surface: [
  774. {
  775. colorscale: [
  776. [0.0, "#0d0887"],
  777. [0.1111111111111111, "#46039f"],
  778. [0.2222222222222222, "#7201a8"],
  779. [0.3333333333333333, "#9c179e"],
  780. [0.4444444444444444, "#bd3786"],
  781. [0.5555555555555556, "#d8576b"],
  782. [0.6666666666666666, "#ed7953"],
  783. [0.7777777777777778, "#fb9f3a"],
  784. [0.8888888888888888, "#fdca26"],
  785. [1.0, "#f0f921"],
  786. ],
  787. type: "surface",
  788. },
  789. ],
  790. table: [
  791. {
  792. cells: {
  793. fill: {
  794. color: "#506784",
  795. },
  796. line: {
  797. color: "rgb(17,17,17)",
  798. },
  799. },
  800. header: {
  801. fill: {
  802. color: "#2a3f5f",
  803. },
  804. line: {
  805. color: "rgb(17,17,17)",
  806. },
  807. },
  808. type: "table",
  809. },
  810. ],
  811. },
  812. layout: {
  813. annotationdefaults: {
  814. arrowcolor: "#f2f5fa",
  815. },
  816. colorscale: {
  817. diverging: [
  818. [0, "#8e0152"],
  819. [0.1, "#c51b7d"],
  820. [0.2, "#de77ae"],
  821. [0.3, "#f1b6da"],
  822. [0.4, "#fde0ef"],
  823. [0.5, "#f7f7f7"],
  824. [0.6, "#e6f5d0"],
  825. [0.7, "#b8e186"],
  826. [0.8, "#7fbc41"],
  827. [0.9, "#4d9221"],
  828. [1, "#276419"],
  829. ],
  830. sequential: [
  831. [0.0, "#0d0887"],
  832. [0.1111111111111111, "#46039f"],
  833. [0.2222222222222222, "#7201a8"],
  834. [0.3333333333333333, "#9c179e"],
  835. [0.4444444444444444, "#bd3786"],
  836. [0.5555555555555556, "#d8576b"],
  837. [0.6666666666666666, "#ed7953"],
  838. [0.7777777777777778, "#fb9f3a"],
  839. [0.8888888888888888, "#fdca26"],
  840. [1.0, "#f0f921"],
  841. ],
  842. sequentialminus: [
  843. [0.0, "#0d0887"],
  844. [0.1111111111111111, "#46039f"],
  845. [0.2222222222222222, "#7201a8"],
  846. [0.3333333333333333, "#9c179e"],
  847. [0.4444444444444444, "#bd3786"],
  848. [0.5555555555555556, "#d8576b"],
  849. [0.6666666666666666, "#ed7953"],
  850. [0.7777777777777778, "#fb9f3a"],
  851. [0.8888888888888888, "#fdca26"],
  852. [1.0, "#f0f921"],
  853. ],
  854. },
  855. colorway: [
  856. "#636efa",
  857. "#EF553B",
  858. "#00cc96",
  859. "#ab63fa",
  860. "#FFA15A",
  861. "#19d3f3",
  862. "#FF6692",
  863. "#B6E880",
  864. "#FF97FF",
  865. "#FECB52",
  866. ],
  867. font: {
  868. color: "#f2f5fa",
  869. },
  870. geo: {
  871. bgcolor: "rgb(17,17,17)",
  872. lakecolor: "rgb(17,17,17)",
  873. landcolor: "rgb(17,17,17)",
  874. subunitcolor: "#506784",
  875. },
  876. mapbox: {
  877. style: "dark",
  878. },
  879. paper_bgcolor: "rgb(17,17,17)",
  880. plot_bgcolor: "rgb(17,17,17)",
  881. polar: {
  882. angularaxis: {
  883. gridcolor: "#506784",
  884. linecolor: "#506784",
  885. },
  886. bgcolor: "rgb(17,17,17)",
  887. radialaxis: {
  888. gridcolor: "#506784",
  889. linecolor: "#506784",
  890. },
  891. },
  892. scene: {
  893. xaxis: {
  894. backgroundcolor: "rgb(17,17,17)",
  895. gridcolor: "#506784",
  896. linecolor: "#506784",
  897. zerolinecolor: "#C8D4E3",
  898. },
  899. yaxis: {
  900. backgroundcolor: "rgb(17,17,17)",
  901. gridcolor: "#506784",
  902. linecolor: "#506784",
  903. zerolinecolor: "#C8D4E3",
  904. },
  905. zaxis: {
  906. backgroundcolor: "rgb(17,17,17)",
  907. gridcolor: "#506784",
  908. linecolor: "#506784",
  909. showbackground: true,
  910. zerolinecolor: "#C8D4E3",
  911. },
  912. },
  913. shapedefaults: {
  914. line: {
  915. color: "#f2f5fa",
  916. },
  917. },
  918. sliderdefaults: {
  919. bgcolor: "#C8D4E3",
  920. bordercolor: "rgb(17,17,17)",
  921. },
  922. ternary: {
  923. aaxis: {
  924. gridcolor: "#506784",
  925. linecolor: "#506784",
  926. },
  927. baxis: {
  928. gridcolor: "#506784",
  929. linecolor: "#506784",
  930. },
  931. bgcolor: "rgb(17,17,17)",
  932. caxis: {
  933. gridcolor: "#506784",
  934. linecolor: "#506784",
  935. },
  936. },
  937. updatemenudefaults: {
  938. bgcolor: "#506784",
  939. },
  940. xaxis: {
  941. gridcolor: "#283442",
  942. linecolor: "#506784",
  943. tickcolor: "#506784",
  944. zerolinecolor: "#283442",
  945. },
  946. yaxis: {
  947. gridcolor: "#283442",
  948. linecolor: "#506784",
  949. tickcolor: "#506784",
  950. zerolinecolor: "#283442",
  951. },
  952. },
  953. };