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]

Reply via email to