GitHub user user1500177 added a comment to the discussion: Inquiry regarding
Candle Chart implementation in the master branch
@dosu , so i sarted to add one for the candle stick chart the major issue ith
the fllwing code i am facing
I also made the edits for the ViZType.ts, the index.ts , the MainPreset.ts
HAVE i missed out on any part @dosu
```
FILE: buildQuery.ts
import {
buildQueryContext,
ensureIsArray,
getColumnLabel,
QueryFormData,
QueryObject,
} from '@superset-ui/core';
// Note: unlike CalendarPie/ThemeRiver's original bug, this file already
// asks the backend to aggregate (via `metrics` + `groupby`), so it does
// NOT need an explicit `groupby: [...]` duplication the way those charts
// did — Superset's query context aggregates automatically when `metrics`
// is populated. This file was never the source of the data-grouping bug.
export default function buildQuery(formData: QueryFormData) {
const groupby = ensureIsArray(formData.groupby);
const metrics = [
formData.open_metric,
formData.close_metric,
formData.low_metric,
formData.high_metric,
].filter(Boolean);
const orderby: [string, boolean][] =
groupby.length > 0 ? [[getColumnLabel(groupby[0]), true]] : [];
return buildQueryContext(formData, (baseQueryObject: QueryObject) => [
{
...baseQueryObject,
metrics,
groupby,
orderby,
},
]);
}
FILE: controlPanel.ts
import { t, validateNonEmpty } from '@superset-ui/core';
import {
ControlPanelConfig,
ControlSetRow,
sharedControls,
} from '@superset-ui/chart-controls';
const config: ControlPanelConfig = {
controlPanelSections: [
{
label: t('Query'),
expanded: true,
controlSetRows: [
['groupby'],
[
{
name: 'open_metric',
config: {
...sharedControls.metrics,
label: t('Open'),
description: t('Metric for the opening price'),
multi: false,
validators: [validateNonEmpty], // was missing — allowed broken
saves
},
},
],
[
{
name: 'close_metric',
config: {
...sharedControls.metrics,
label: t('Close'),
description: t('Metric for the closing price'),
multi: false,
validators: [validateNonEmpty],
},
},
],
[
{
name: 'low_metric',
config: {
...sharedControls.metrics,
label: t('Low'),
description: t('Metric for the lowest price'),
multi: false,
validators: [validateNonEmpty],
},
},
],
[
{
name: 'high_metric',
config: {
...sharedControls.metrics,
label: t('High'),
description: t('Metric for the highest price'),
multi: false,
validators: [validateNonEmpty],
},
},
],
['adhoc_filters'],
['row_limit'],
] as ControlSetRow[],
},
{
label: t('Chart Options'),
expanded: true,
controlSetRows: [
['y_axis_format'],
['currency_format'],
] as ControlSetRow[],
},
],
};
export default config;
FILE: EchartsCandlestick.tsx
import Echart from '../components/Echart';
import { allEventHandlers } from '../utils/eventHandlers';
import { CandlestickTransformedProps } from './types';
// FIX: matches EchartsBoxPlot.tsx exactly now — refs come from props
// (built in transformProps), eventHandlers wired up via allEventHandlers,
// selectedValues passed through. The old version built its own local
// useRef and discarded everything transformProps constructed.
export default function EchartsCandlestick(
props: CandlestickTransformedProps,
) {
const { width, height, echartOptions, refs, selectedValues, data = [] } =
props;
const eventHandlers = allEventHandlers(props);
if (!data.length) {
return (
<div
style={{
width,
height,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
color: '#666',
}}
>
No data
</div>
);
}
return (
<Echart
refs={refs}
width={width}
height={height}
echartOptions={echartOptions}
eventHandlers={eventHandlers}
selectedValues={selectedValues}
/>
);
}
FILE: index.ts
import { Behavior, t } from '@superset-ui/core';
import { EchartsChartPlugin } from '../types';
import buildQuery from './buildQuery';
import controlPanel from './controlPanel';
import transformProps from './transformProps';
import thumbnail from './images/thumbnail.png';
import example1 from './images/example1.png';
import { CandlestickChartProps, CandlestickFormData } from './types';
// FIX: added the <CandlestickFormData, CandlestickChartProps> generics —
// BoxPlot's plugin class does the same; without them the plugin's typing
// isn't tied to your form data shape.
export default class EchartsCandlestickPlugin extends EchartsChartPlugin<
CandlestickFormData,
CandlestickChartProps
> {
constructor() {
super({
metadata: {
behaviors: [
Behavior.InteractiveChart,
Behavior.DrillToDetail,
Behavior.DrillBy,
],
credits: ['https://echarts.apache.org'],
name: t('Candlestick'),
description: t(
'Displays OHLC (Open, High, Low, Close) price data as candlestick
bars over time.',
),
category: t('Financial'),
tags: [t('Financial'), t('ECharts'), t('Stock'), t('OHLC')],
thumbnail,
exampleGallery: [{ url: example1 }],
},
controlPanel,
buildQuery,
loadChart: () => import('./EchartsCandlestick'),
transformProps,
});
}
}
FILE: transformProps.ts
import {
GenericDataType,
getColumnLabel,
getMetricLabel,
getTimeFormatter,
tooltipHtml,
} from '@superset-ui/core';
import type { EChartsCoreOption } from 'echarts/core';
import { extractGroupbyLabel, getColtypesMapping } from '../utils/series';
import { getDefaultTooltip } from '../utils/tooltip';
import { Refs } from '../types';
import {
CandlestickChartProps,
CandlestickDataItem,
CandlestickTransformedProps,
} from './types';
const dayFormatter = getTimeFormatter('%Y-%m-%d');
export default function transformProps(
chartProps: CandlestickChartProps,
): CandlestickTransformedProps {
// FIX: now destructures hooks/filterState/emitCrossFilters/inContextMenu
// — the dashboard framework provides these on every chart render; the
// old version ignored them entirely, same gap BoxPlot never has.
const {
width,
height,
formData,
queriesData,
hooks,
filterState,
emitCrossFilters,
inContextMenu,
} = chartProps;
const {
open_metric,
close_metric,
low_metric,
high_metric,
groupby = [],
} = formData;
const refs: Refs = {};
const { setDataMask = () => {}, onContextMenu } = hooks || {};
const { data = [], colnames = [] } = queriesData[0];
const coltypeMapping = getColtypesMapping(queriesData[0]);
const openKey = open_metric
? getMetricLabel(open_metric)
: (colnames[1] ?? '');
const closeKey = close_metric
? getMetricLabel(close_metric)
: (colnames[2] ?? '');
const lowKey = low_metric
? getMetricLabel(low_metric)
: (colnames[3] ?? '');
const highKey = high_metric
? getMetricLabel(high_metric)
: (colnames[4] ?? '');
const dateKey =
groupby.length > 0 ? getColumnLabel(groupby[0]) : (colnames[0] ?? '');
const dateColType = coltypeMapping[dateKey];
// Standard coltypes-based date normalization — same convention as
// CalendarPie/ThemeRiver. Was previously raw `new Date(...).toISOString()`.
const toDayString = (raw: unknown): string => {
if (raw === null || raw === undefined) {
return '';
}
if (dateColType === GenericDataType.Temporal) {
const epoch = typeof raw === 'string' ? Number.parseInt(raw, 10) : raw;
if (typeof epoch === 'number' && !Number.isNaN(epoch)) {
return dayFormatter(epoch);
}
}
return String(raw).split(' ')[0].split('T')[0];
};
const candleData: CandlestickDataItem[] = data.map((record: any) => ({
date: toDayString(record[dateKey]),
// ECharts candlestick data order per point: [open, close, low, high] —
// matches the official example you pasted exactly.
ohlc: [
Number(record[openKey]) || 0,
Number(record[closeKey]) || 0,
Number(record[lowKey]) || 0,
Number(record[highKey]) || 0,
] as [number, number, number, number],
}));
const dates = candleData.map(item => item.date);
const candleSeriesData = candleData.map(item => item.ohlc);
// Minimal cross-filter wiring — enables context menu / drill-to-detail
// and keeps the type contract satisfied, same level of support ThemeRiver
// got (this chart's primary axis is a date category, not a natural
// multi-select groupby dimension, so full click-to-filter isn't the
// priority here — structural correctness is).
const groupbyLabels = groupby.map(getColumnLabel);
const labelMap = data.reduce((acc: Record<string, string[]>, datum: any) => {
const label = extractGroupbyLabel({
datum,
groupby: groupbyLabels,
coltypeMapping,
timeFormatter: dayFormatter,
});
return { ...acc, [label]: groupbyLabels.map(col => datum[col] as string) };
}, {});
const selectedValues = (filterState?.selectedValues || []).reduce(
(acc: Record<string, number>, selectedValue: string) => {
const index = dates.findIndex(d => d === selectedValue);
return { ...acc, [index]: selectedValue };
},
{},
);
const echartOptions: EChartsCoreOption = {
tooltip: {
...getDefaultTooltip(refs),
show: !inContextMenu,
trigger: 'axis',
axisPointer: { type: 'cross' },
formatter: (params: any) => {
const arr = Array.isArray(params) ? params : [params];
const rows: [string, string][] = [];
let title = '';
arr.forEach((p: any) => {
// ECharts prepends the category index as element [0] when a
// candlestick series' data is a plain array-of-arrays — real
// OHLC values are always the LAST 4 entries.
const raw = p.data as number[];
const [open, close, low, high] = raw.slice(-4);
title = p.axisValue;
rows.push(['Open', String(open)]);
rows.push(['Close', String(close)]);
rows.push(['Low', String(low)]);
rows.push(['High', String(high)]);
});
return tooltipHtml(rows, title);
},
},
xAxis: {
type: 'category',
data: dates,
boundaryGap: true,
axisLabel: { rotate: 45, hideOverlap: true },
},
yAxis: {
scale: true,
splitNumber: 3,
},
grid: {
left: 60,
right: 40,
top: 60,
bottom: 80,
containLabel: true,
},
series: [
{
type: 'candlestick',
data: candleSeriesData,
itemStyle: {
// Kept your original colors exactly — see README for what
// color/color0 mean and which market convention this follows.
color: '#ec0000',
color0: '#00da3c',
borderColor: '#8A0000',
borderColor0: '#008F28',
},
},
],
};
return {
width,
height,
formData,
data: candleData,
echartOptions,
refs,
setDataMask,
onContextMenu,
emitCrossFilters,
selectedValues,
groupby: groupbyLabels,
labelMap,
coltypeMapping,
};
}
FILE: types.ts
import { QueryFormColumn, QueryFormData, QueryFormMetric } from
'@superset-ui/core';
import {
BaseChartProps,
BaseTransformedProps,
ContextMenuTransformedProps,
CrossFilterTransformedProps,
} from '../types';
export type CandlestickFormData = QueryFormData & {
open_metric?: QueryFormMetric;
close_metric?: QueryFormMetric;
low_metric?: QueryFormMetric;
high_metric?: QueryFormMetric;
groupby?: QueryFormColumn[];
y_axis_format?: string;
currency_format?: string;
};
export interface CandlestickDataItem {
date: string;
ohlc: [number, number, number, number]; // [open, close, low, high]
}
// FIX: was a custom interface with `refs: Record<string, any>` — now
// built the same way BoxPlot's types are: BaseTransformedProps supplies
// refs/echartOptions/width/height/formData; CrossFilterTransformedProps
// and ContextMenuTransformedProps add the dashboard hook contract.
export interface CandlestickChartProps
extends BaseChartProps<CandlestickFormData> {
formData: CandlestickFormData;
}
export type CandlestickTransformedProps =
BaseTransformedProps<CandlestickFormData> &
CrossFilterTransformedProps &
ContextMenuTransformedProps & {
data: CandlestickDataItem[]; // kept for the "No data" empty state
};
```
I CAN SEE the chart in the locat chart section save it , export it etc , but
the ONLY issue i am facing is the fllwing if i add it to a dashboar on the
first load the cahrt is not visible - where as the boxplot , timeseries charts
are visible
<img width="1763" height="950" alt="image"
src="https://github.com/user-attachments/assets/0aa3f6aa-f025-44fe-b2cf-a8ae7a653c6f"
/>
BUT after that if i go and click on the edit chartt the same cahrt HAS some
data and its seen in the chart even when i comback its seen in dashboard, at
tthe momone when i hit the Ctrl+R or reload my browser THE candle stick cahrt
alone vaninshes
BUT on clicking view as table and all the same cahrt shows the data , -only in
the UI its MISSING the require info
<img width="1861" height="950" alt="image"
src="https://github.com/user-attachments/assets/58be1bf6-d490-436f-bd5c-754ab11551d6"
/>
<img width="1906" height="1034" alt="image"
src="https://github.com/user-attachments/assets/028750bf-a26d-4d51-b2ab-e2667892d5a9"
/>
WHAt is hppening , did i miss to add something - CAN I get a proper
DOCUMENTATION on the steps to more and more chart form the echarts website to
the same echart plugin superset alrady HAVE ?
GitHub link:
https://github.com/apache/superset/discussions/37653#discussioncomment-17807667
----
This is an automatically sent email for [email protected].
To unsubscribe, please send an email to:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]