Skip to content

Commit

Permalink
Add 'click' functionality for Chart visualization (getredash#5662)
Browse files Browse the repository at this point in the history
Co-authored-by: Jesse <jwhitehouse@airpost.net>
Co-authored-by: Justin Clift <justin@postgresql.org>
Reviewed-by: Eric Radman <eradman@starfishstorage.com>
  • Loading branch information
3 people authored and harveyrendell committed Jan 8, 2025
1 parent d3bceb5 commit a360373
Show file tree
Hide file tree
Showing 5 changed files with 115 additions and 2 deletions.
17 changes: 17 additions & 0 deletions viz-lib/src/visualizations/chart/Editor/GeneralSettings.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 } });
});


});
60 changes: 59 additions & 1 deletion viz-lib/src/visualizations/chart/Editor/GeneralSettings.tsx
Original file line number Diff line number Diff line change
@@ -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"];
Expand Down Expand Up @@ -122,6 +123,8 @@ export default function GeneralSettings({ options, data, onOptionsChange }: any)
onOptionsChange({ swappedAxes: !options.swappedAxes, seriesOptions });
}

const [debouncedOnOptionsChange] = useDebouncedCallback(onOptionsChange, 200);

return (
<React.Fragment>
{/* @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 */}
Expand Down Expand Up @@ -339,6 +342,61 @@ export default function GeneralSettings({ options, data, onOptionsChange }: any)
</Select>
</Section>
)}

{/* @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 */}
<Section>
<Checkbox
data-test="Chart.EnableClickEvents"
defaultChecked={options.enableLink}
onChange={event => onOptionsChange({ enableLink: event.target.checked })}>
Enable click events
</Checkbox>
</Section>

{/* @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 */}
<Section>
<Checkbox
data-test="Chart.EnableClickEvents.NewTab"
defaultChecked={options.linkOpenNewTab}
onChange={event => onOptionsChange({ linkOpenNewTab: event.target.checked })}
disabled={!(options.enableLink === true)}
>
Open in new tab
</Checkbox>
</Section>

{/* @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 */}
<Section>
<Input
label={
<React.Fragment>
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 */}
<ContextHelp
placement="topLeft"
arrowPointAtCenter
// @ts-expect-error ts-migrate(2322) FIXME: Type 'Element' is not assignable to type 'null | u... Remove this comment to see the full error message
icon={ContextHelp.defaultIcon}>
<div>
Every curve can be referenced using <code>{"{{ @@x1 }} {{ @@y1 }} {{ @@x2 }} {{ @@y2 }} ..."}</code> syntax:<br/>
axis with any curve number according to the Series config.
</div>
<div>
The first met curve X and Y values can be referenced by just<code>{"{{ @@x }} {{ @@y }}"}</code> syntax.
</div>
<div>
Any unresolved reference would be replaced with an empty string.
</div>
</ContextHelp>
</React.Fragment>
}
data-test="Chart.DataLabels.TextFormat"
placeholder="(nothing)"
defaultValue={options.linkFormat}
onChange={(e: any) => debouncedOnOptionsChange({ linkFormat: e.target.value })}
disabled={!(options.enableLink === true)}
/>
</Section>
</React.Fragment>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {},
Expand Down
30 changes: 29 additions & 1 deletion viz-lib/src/visualizations/chart/Renderer/initChart.ts
Original file line number Diff line number Diff line change
@@ -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) => {
Expand Down Expand Up @@ -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,
Expand Down
4 changes: 4 additions & 0 deletions viz-lib/src/visualizations/chart/getOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};

Expand Down

0 comments on commit a360373

Please sign in to comment.