You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by ru...@apache.org on 2025/08/17 17:12:28 UTC
(superset) 05/06: feat: Create TRUE React-based control panel for Pie chart
This is an automated email from the ASF dual-hosted git repository.
rusackas pushed a commit to branch move-controls
in repository https://gitbox.apache.org/repos/asf/superset.git
commit 3603775df1e63a12c0794bb1502a67cbdb6a82e1
Author: Evan Rusackas <ev...@rusackas.com>
AuthorDate: Thu Aug 14 10:47:51 2025 -0700
feat: Create TRUE React-based control panel for Pie chart
- Complete abandonment of controlPanelSections/controlSetRows architecture
- Use Ant Design Collapse and Grid for layout instead of proprietary constructs
- Import actual React control components from controlMap
- Direct component usage, no config objects
- Clean, modern React component approach with proper props
This is the actual React-based control panel we want - pure components,
no legacy structures, just React + Ant Design.
---
.../src/Pie/PieControlPanel.tsx | 433 +++++++++++++++++++++
.../plugins/plugin-chart-echarts/src/Pie/index.ts | 4 +-
2 files changed, 435 insertions(+), 2 deletions(-)
diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Pie/PieControlPanel.tsx b/superset-frontend/plugins/plugin-chart-echarts/src/Pie/PieControlPanel.tsx
new file mode 100644
index 0000000000..a3fce3338f
--- /dev/null
+++ b/superset-frontend/plugins/plugin-chart-echarts/src/Pie/PieControlPanel.tsx
@@ -0,0 +1,433 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import { FC } from 'react';
+import { t } from '@superset-ui/core';
+import { Collapse, Row, Col, Typography } from 'antd';
+import {
+ ControlPanelConfig,
+ D3_FORMAT_OPTIONS,
+ D3_NUMBER_FORMAT_DESCRIPTION_VALUES_TEXT,
+ D3_TIME_FORMAT_OPTIONS,
+ D3_FORMAT_DOCS,
+} from '@superset-ui/chart-controls';
+
+// Import the control map which contains all control components
+import controlMap from 'src/explore/components/controls';
+
+import { DEFAULT_FORM_DATA } from './types';
+
+const { Panel } = Collapse;
+const { Title } = Typography;
+
+interface PieControlPanelProps {
+ onChange: (field: string, value: any) => void;
+ value: Record<string, any>;
+ datasource: any;
+ actions?: any;
+ controls?: any;
+ form_data?: any;
+}
+
+/**
+ * A TRUE React component-based control panel for Pie charts.
+ * No legacy controlPanelSections, no controlSetRows, no config objects.
+ * Just pure React components with Ant Design layout.
+ */
+export const PieControlPanel: FC<PieControlPanelProps> = ({
+ onChange,
+ value,
+ datasource,
+ form_data,
+}) => {
+ // Get control components from controlMap
+ const DndColumnSelect = controlMap.DndColumnSelect;
+ const DndMetricSelect = controlMap.DndMetricSelect;
+ const AdhocFilterControl = controlMap.AdhocFilterControl;
+ const CheckboxControl = controlMap.CheckboxControl;
+ const SelectControl = controlMap.SelectControl;
+ const TextControl = controlMap.TextControl;
+ const SliderControl = controlMap.SliderControl;
+ const ColorSchemeControl = controlMap.ColorSchemeControl;
+
+ // Helper to handle control changes
+ const handleChange = (field: string) => (val: any) => {
+ onChange(field, val);
+ };
+
+ return (
+ <div style={{ padding: '16px' }}>
+ <Collapse defaultActiveKey={['query', 'chart', 'labels', 'pie', 'legend']} ghost>
+ {/* Query Section */}
+ <Panel header={<Title level={5}>{t('Query')}</Title>} key="query">
+ <Row gutter={[16, 16]}>
+ <Col span={24}>
+ <DndColumnSelect
+ name="groupby"
+ label={t('Group by')}
+ onChange={handleChange('groupby')}
+ value={value.groupby || []}
+ datasource={datasource}
+ multi
+ />
+ </Col>
+ </Row>
+
+ <Row gutter={[16, 16]} style={{ marginTop: 16 }}>
+ <Col span={24}>
+ <DndMetricSelect
+ name="metric"
+ label={t('Metric')}
+ onChange={handleChange('metric')}
+ value={value.metric}
+ datasource={datasource}
+ multi={false}
+ />
+ </Col>
+ </Row>
+
+ <Row gutter={[16, 16]} style={{ marginTop: 16 }}>
+ <Col span={24}>
+ <AdhocFilterControl
+ name="adhoc_filters"
+ label={t('Filters')}
+ onChange={handleChange('adhoc_filters')}
+ value={value.adhoc_filters || []}
+ datasource={datasource}
+ />
+ </Col>
+ </Row>
+
+ <Row gutter={[16, 16]} style={{ marginTop: 16 }}>
+ <Col span={12}>
+ <TextControl
+ label={t('Row limit')}
+ onChange={handleChange('row_limit')}
+ value={value.row_limit || 100}
+ controlId="row_limit"
+ renderTrigger
+ />
+ </Col>
+ <Col span={12}>
+ <CheckboxControl
+ label={t('Sort by metric')}
+ onChange={handleChange('sort_by_metric')}
+ value={value.sort_by_metric ?? true}
+ controlId="sort_by_metric"
+ renderTrigger
+ />
+ </Col>
+ </Row>
+ </Panel>
+
+ {/* Chart Options */}
+ <Panel header={<Title level={5}>{t('Chart Options')}</Title>} key="chart">
+ <Row gutter={[16, 16]}>
+ <Col span={24}>
+ <ColorSchemeControl
+ name="color_scheme"
+ label={t('Color scheme')}
+ onChange={handleChange('color_scheme')}
+ value={value.color_scheme || 'supersetColors'}
+ schemes={() => {}}
+ isLinear={false}
+ />
+ </Col>
+ </Row>
+
+ <Row gutter={[16, 16]} style={{ marginTop: 16 }}>
+ <Col span={12}>
+ <TextControl
+ label={t('Percentage threshold')}
+
+ onChange={handleChange('show_labels_threshold')}
+ value={value.show_labels_threshold ?? 5}
+ controlId="show_labels_threshold"
+ renderTrigger
+
+ />
+ </Col>
+ <Col span={12}>
+ <TextControl
+ label={t('Threshold for Other')}
+
+ onChange={handleChange('threshold_for_other')}
+ value={value.threshold_for_other ?? 0}
+ controlId="threshold_for_other"
+ renderTrigger
+
+ />
+ </Col>
+ </Row>
+
+ <Row gutter={[16, 16]} style={{ marginTop: 16 }}>
+ <Col span={12}>
+ <SelectControl
+ label={t('Rose Type')}
+
+ onChange={handleChange('roseType')}
+ value={value.roseType || null}
+ choices={[
+ ['area', t('Area')],
+ ['radius', t('Radius')],
+ [null, t('None')],
+ ]}
+ clearable={false}
+ />
+ </Col>
+ </Row>
+ </Panel>
+
+ {/* Labels Section */}
+ <Panel header={<Title level={5}>{t('Labels')}</Title>} key="labels">
+ <Row gutter={[16, 16]}>
+ <Col span={24}>
+ <SelectControl
+ label={t('Label Type')}
+
+ onChange={handleChange('label_type')}
+ value={value.label_type || 'key'}
+ choices={[
+ ['key', t('Category Name')],
+ ['value', t('Value')],
+ ['percent', t('Percentage')],
+ ['key_value', t('Category and Value')],
+ ['key_percent', t('Category and Percentage')],
+ ['key_value_percent', t('Category, Value and Percentage')],
+ ['value_percent', t('Value and Percentage')],
+ ['template', t('Template')],
+ ]}
+ clearable={false}
+ />
+ </Col>
+ </Row>
+
+ {value.label_type === 'template' && (
+ <Row gutter={[16, 16]} style={{ marginTop: 16 }}>
+ <Col span={24}>
+ <TextControl
+ label={t('Label Template')}
+ onChange={handleChange('label_template')}
+ value={value.label_template || ''}
+ controlId="label_template"
+ renderTrigger
+ />
+ </Col>
+ </Row>
+ )}
+
+ <Row gutter={[16, 16]} style={{ marginTop: 16 }}>
+ <Col span={12}>
+ <SelectControl
+ label={t('Number format')}
+
+ onChange={handleChange('number_format')}
+ value={value.number_format || 'SMART_NUMBER'}
+ choices={D3_FORMAT_OPTIONS}
+ freeForm
+ />
+ </Col>
+ <Col span={12}>
+ <SelectControl
+ label={t('Date format')}
+
+ onChange={handleChange('date_format')}
+ value={value.date_format || 'smart_date'}
+ choices={D3_TIME_FORMAT_OPTIONS}
+ freeForm
+ />
+ </Col>
+ </Row>
+
+ <Row gutter={[16, 16]} style={{ marginTop: 16 }}>
+ <Col span={8}>
+ <CheckboxControl
+ label={t('Show Labels')}
+
+ onChange={handleChange('show_labels')}
+ value={value.show_labels ?? DEFAULT_FORM_DATA.showLabels}
+ controlId="show_labels"
+ renderTrigger
+ />
+ </Col>
+ <Col span={8}>
+ <CheckboxControl
+ label={t('Put labels outside')}
+
+ onChange={handleChange('labels_outside')}
+ value={value.labels_outside ?? DEFAULT_FORM_DATA.labelsOutside}
+ controlId="labels_outside"
+ renderTrigger
+ />
+ </Col>
+ <Col span={8}>
+ <CheckboxControl
+ label={t('Label Line')}
+
+ onChange={handleChange('label_line')}
+ value={value.label_line ?? DEFAULT_FORM_DATA.labelLine}
+ controlId="label_line"
+ renderTrigger
+ />
+ </Col>
+ </Row>
+
+ <Row gutter={[16, 16]} style={{ marginTop: 16 }}>
+ <Col span={12}>
+ <CheckboxControl
+ label={t('Show Total')}
+
+ onChange={handleChange('show_total')}
+ value={value.show_total ?? false}
+ controlId="show_total"
+ renderTrigger
+ />
+ </Col>
+ </Row>
+ </Panel>
+
+ {/* Pie Shape Section */}
+ <Panel header={<Title level={5}>{t('Pie shape')}</Title>} key="pie">
+ <Row gutter={[16, 16]}>
+ <Col span={24}>
+ <SliderControl
+ label={t('Outer Radius')}
+ onChange={handleChange('outerRadius')}
+ value={value.outerRadius ?? DEFAULT_FORM_DATA.outerRadius}
+ />
+ </Col>
+ </Row>
+
+ <Row gutter={[16, 16]} style={{ marginTop: 16 }}>
+ <Col span={12}>
+ <CheckboxControl
+ label={t('Donut')}
+
+ onChange={handleChange('donut')}
+ value={value.donut ?? DEFAULT_FORM_DATA.donut}
+ controlId="donut"
+ renderTrigger
+ />
+ </Col>
+ </Row>
+
+ {value.donut && (
+ <Row gutter={[16, 16]} style={{ marginTop: 16 }}>
+ <Col span={24}>
+ <SliderControl
+ label={t('Inner Radius')}
+ onChange={handleChange('innerRadius')}
+ value={value.innerRadius ?? DEFAULT_FORM_DATA.innerRadius}
+ />
+ </Col>
+ </Row>
+ )}
+ </Panel>
+
+ {/* Legend Section */}
+ <Panel header={<Title level={5}>{t('Legend')}</Title>} key="legend">
+ <Row gutter={[16, 16]}>
+ <Col span={24}>
+ <CheckboxControl
+ label={t('Show legend')}
+
+ onChange={handleChange('show_legend')}
+ value={value.show_legend ?? true}
+ controlId="show_legend"
+ renderTrigger
+ />
+ </Col>
+ </Row>
+
+ {value.show_legend && (
+ <>
+ <Row gutter={[16, 16]} style={{ marginTop: 16 }}>
+ <Col span={12}>
+ <SelectControl
+ label={t('Legend type')}
+
+ onChange={handleChange('legendType')}
+ value={value.legendType || 'scroll'}
+ choices={[
+ ['scroll', t('Scroll')],
+ ['plain', t('Plain')],
+ ]}
+ clearable={false}
+ />
+ </Col>
+ <Col span={12}>
+ <SelectControl
+ label={t('Legend orientation')}
+
+ onChange={handleChange('legendOrientation')}
+ value={value.legendOrientation || 'top'}
+ choices={[
+ ['top', t('Top')],
+ ['bottom', t('Bottom')],
+ ['left', t('Left')],
+ ['right', t('Right')],
+ ]}
+ clearable={false}
+ />
+ </Col>
+ </Row>
+
+ <Row gutter={[16, 16]} style={{ marginTop: 16 }}>
+ <Col span={24}>
+ <TextControl
+ label={t('Legend margin')}
+
+ onChange={handleChange('legendMargin')}
+ value={value.legendMargin || 0}
+ controlId="legendMargin"
+ renderTrigger
+
+ />
+ </Col>
+ </Row>
+ </>
+ )}
+ </Panel>
+ </Collapse>
+ </div>
+ );
+};
+
+/**
+ * Export as a ControlPanelConfig that just contains our React component.
+ * This is the bridge between the old system and our new approach.
+ */
+const config: ControlPanelConfig = {
+ controlPanelSections: [
+ {
+ label: t('React Control Panel'),
+ expanded: true,
+ controlSetRows: [
+ [
+ <PieControlPanel
+ onChange={() => {}}
+ value={{}}
+ datasource={{}}
+ />,
+ ],
+ ],
+ },
+ ],
+};
+
+export default config;
\ No newline at end of file
diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Pie/index.ts b/superset-frontend/plugins/plugin-chart-echarts/src/Pie/index.ts
index faaf3569b3..53e0319929 100644
--- a/superset-frontend/plugins/plugin-chart-echarts/src/Pie/index.ts
+++ b/superset-frontend/plugins/plugin-chart-echarts/src/Pie/index.ts
@@ -18,8 +18,8 @@
*/
import { Behavior, t } from '@superset-ui/core';
import buildQuery from './buildQuery';
-// Use the modern control panel with proper Redux integration
-import controlPanel from './controlPanelModern';
+// Use the TRUE React component-based control panel
+import controlPanel from './PieControlPanel';
import transformProps from './transformProps';
import thumbnail from './images/thumbnail.png';
import example1 from './images/Pie1.jpg';