diff --git a/viz-lib/src/visualizations/chart/Editor/GeneralSettings.test.tsx b/viz-lib/src/visualizations/chart/Editor/GeneralSettings.test.tsx index 3563f7ab09..95da74fb44 100644 --- a/viz-lib/src/visualizations/chart/Editor/GeneralSettings.test.tsx +++ b/viz-lib/src/visualizations/chart/Editor/GeneralSettings.test.tsx @@ -212,4 +212,21 @@ describe("Visualizations -> Chart -> Editor -> General Settings", () => { .find("input") .simulate("change", { target: { checked: true } }); }); + + test("Toggles Enable click events", done => { + const el = mount( + { + globalSeriesType: "column", + series: {}, + }, + done + ); + + findByTestID(el, "Chart.EnableClickEvents") + .last() + .find("input") + .simulate("change", { target: { checked: true } }); + }); + + }); diff --git a/viz-lib/src/visualizations/chart/Editor/GeneralSettings.tsx b/viz-lib/src/visualizations/chart/Editor/GeneralSettings.tsx index 365e20c484..03e12d5a66 100644 --- a/viz-lib/src/visualizations/chart/Editor/GeneralSettings.tsx +++ b/viz-lib/src/visualizations/chart/Editor/GeneralSettings.tsx @@ -1,11 +1,12 @@ import { isArray, map, mapValues, includes, some, each, difference, toNumber } from "lodash"; import React, { useMemo } from "react"; -import { Section, Select, Checkbox, InputNumber } from "@/components/visualizations/editor"; +import { Section, Select, Checkbox, InputNumber, ContextHelp, Input } from "@/components/visualizations/editor"; import { UpdateOptionsStrategy } from "@/components/visualizations/editor/createTabbedEditor"; import { EditorPropTypes } from "@/visualizations/prop-types"; import ChartTypeSelect from "./ChartTypeSelect"; import ColumnMappingSelect from "./ColumnMappingSelect"; +import { useDebouncedCallback } from "use-debounce/lib"; function getAvailableColumnMappingTypes(options: any) { const result = ["x", "y"]; @@ -122,6 +123,8 @@ export default function GeneralSettings({ options, data, onOptionsChange }: any) onOptionsChange({ swappedAxes: !options.swappedAxes, seriesOptions }); } + const [debouncedOnOptionsChange] = useDebouncedCallback(onOptionsChange, 200); + return ( {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */} @@ -339,6 +342,61 @@ export default function GeneralSettings({ options, data, onOptionsChange }: any) )} + + {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */} +
+ onOptionsChange({ enableLink: event.target.checked })}> + Enable click events + +
+ + {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */} +
+ onOptionsChange({ linkOpenNewTab: event.target.checked })} + disabled={!(options.enableLink === true)} + > + Open in new tab + +
+ + {/* @ts-expect-error ts-migrate(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */} +
+ + URL template + {/* @ts-expect-error ts-migrate(2746) FIXME: This JSX tag's 'children' prop expects a single ch... Remove this comment to see the full error message */} + +
+ Every curve can be referenced using {"{{ @@x1 }} {{ @@y1 }} {{ @@x2 }} {{ @@y2 }} ..."} syntax:
+ axis with any curve number according to the Series config. +
+
+ The first met curve X and Y values can be referenced by just{"{{ @@x }} {{ @@y }}"} syntax. +
+
+ Any unresolved reference would be replaced with an empty string. +
+
+ + } + data-test="Chart.DataLabels.TextFormat" + placeholder="(nothing)" + defaultValue={options.linkFormat} + onChange={(e: any) => debouncedOnOptionsChange({ linkFormat: e.target.value })} + disabled={!(options.enableLink === true)} + /> +
); } diff --git a/viz-lib/src/visualizations/chart/Editor/__snapshots__/GeneralSettings.test.tsx.snap b/viz-lib/src/visualizations/chart/Editor/__snapshots__/GeneralSettings.test.tsx.snap index a520b2d38e..0e9f5ff157 100644 --- a/viz-lib/src/visualizations/chart/Editor/__snapshots__/GeneralSettings.test.tsx.snap +++ b/viz-lib/src/visualizations/chart/Editor/__snapshots__/GeneralSettings.test.tsx.snap @@ -44,6 +44,12 @@ Object { } `; +exports[`Visualizations -> Chart -> Editor -> General Settings Toggles Enable click events 1`] = ` +Object { + "enableLink": true, +} +`; + exports[`Visualizations -> Chart -> Editor -> General Settings Toggles horizontal bar chart 1`] = ` Object { "seriesOptions": Object {}, diff --git a/viz-lib/src/visualizations/chart/Renderer/initChart.ts b/viz-lib/src/visualizations/chart/Renderer/initChart.ts index aec9bc6b4e..4c1e7d45cb 100644 --- a/viz-lib/src/visualizations/chart/Renderer/initChart.ts +++ b/viz-lib/src/visualizations/chart/Renderer/initChart.ts @@ -1,6 +1,12 @@ -import { isArray, isObject, isString, isFunction, startsWith, reduce, merge, map, each } from "lodash"; +import { isArray, isObject, isString, isFunction, startsWith, reduce, merge, map, each, isNil } from "lodash"; import resizeObserver from "@/services/resizeObserver"; import { Plotly, prepareData, prepareLayout, updateData, updateAxes, updateChartSize } from "../plotly"; +import { formatSimpleTemplate } from "@/lib/value-format"; + +const navigateToUrl = (url: string, shouldOpenNewTab: boolean = true) => + shouldOpenNewTab + ? window.open(url, "_blank") + : window.location.href = url; function createErrorHandler(errorHandler: any) { return (error: any) => { @@ -110,6 +116,28 @@ export default function initChart(container: any, options: any, data: any, addit ); options.onHover && container.on("plotly_hover", options.onHover); options.onUnHover && container.on("plotly_unhover", options.onUnHover); + container.on('plotly_click', + createSafeFunction((data: any) => { + if (options.enableLink === true) { + try { + var templateValues: { [k: string]: any } = {} + data.points.forEach((point: any, i: number) => { + var sourceDataElement = [...point.data?.sourceData?.entries()][point.pointNumber ?? 0]?.[1]?.row ?? {}; + + if (isNil(templateValues['@@x'])) templateValues['@@x'] = sourceDataElement.x; + if (isNil(templateValues['@@y'])) templateValues['@@y'] = sourceDataElement.y; + + templateValues[`@@y${i + 1}`] = sourceDataElement.y; + templateValues[`@@x${i + 1}`] = sourceDataElement.x; + }) + navigateToUrl( + formatSimpleTemplate(options.linkFormat, templateValues).replace(/{{\s*([^\s]+?)\s*}}/g, () => ''), + options.linkOpenNewTab); + } catch (error) { + console.error('Click error: [%s]', error.message, { error }); + } + } + })); unwatchResize = resizeObserver( container, diff --git a/viz-lib/src/visualizations/chart/getOptions.ts b/viz-lib/src/visualizations/chart/getOptions.ts index 4c272ff7b0..531379d066 100644 --- a/viz-lib/src/visualizations/chart/getOptions.ts +++ b/viz-lib/src/visualizations/chart/getOptions.ts @@ -23,6 +23,10 @@ const DEFAULT_OPTIONS = { // dateTimeFormat: 'DD/MM/YYYY HH:mm', // will be set from visualizationsSettings textFormat: "", // default: combination of {{ @@yPercent }} ({{ @@y }} ± {{ @@yError }}) + enableLink: false, + linkOpenNewTab: true, + linkFormat: "", // template like a textFormat + missingValuesAsZero: true, };