| /** |
| * 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, useState } from 'react'; |
| import { t } from '@superset-ui/core'; |
| import { Tabs } from 'antd'; |
| import { |
| ColorSchemeControl, |
| D3_TIME_FORMAT_OPTIONS, |
| D3_FORMAT_OPTIONS, |
| D3_TIME_FORMAT_DOCS, |
| TimeShiftColorControl, |
| YAxisFormatControl, |
| CurrencyFormatControl, |
| ZoomableControl, |
| // Import all control components from chart-controls package |
| DndColumnSelect, |
| DndMetricSelect, |
| DndFilterSelect, |
| TextControl, |
| CheckboxControl, |
| SliderControl, |
| SelectControl, |
| ControlHeader, |
| Control, |
| } from '@superset-ui/chart-controls'; |
| |
| import { EchartsTimeseriesSeriesType } from '../../types'; |
| import { TIME_SERIES_DESCRIPTION_TEXT } from '../../constants'; |
| |
| interface LineControlPanelProps { |
| onChange?: (field: string, value: any) => void; |
| value?: Record<string, any>; |
| datasource?: any; |
| actions?: any; |
| controls?: any; |
| form_data?: any; |
| } |
| |
| export const LineControlPanel: FC<LineControlPanelProps> = ({ |
| onChange, |
| value, |
| datasource, |
| form_data, |
| actions, |
| controls, |
| }) => { |
| // State hooks must be called before any early returns |
| const [activeTab, setActiveTab] = useState('data'); |
| |
| // Safety checks for datasource |
| if (!datasource || !form_data) { |
| return <div>Loading control panel...</div>; |
| } |
| |
| // Ensure safe data structures |
| const safeColumns = Array.isArray(datasource?.columns) |
| ? datasource.columns |
| : []; |
| const safeMetrics = Array.isArray(datasource?.metrics) |
| ? datasource.metrics |
| : []; |
| |
| // Helper for control changes |
| const handleChange = (field: string) => (val: any) => { |
| if (actions?.setControlValue) { |
| actions.setControlValue(field, val); |
| } else if (onChange) { |
| onChange(field, val); |
| } |
| }; |
| |
| // Get form values |
| const formValues = form_data || value || {}; |
| |
| const dataTabContent = ( |
| <div> |
| {/* Query section */} |
| <div style={{ marginBottom: 24 }}> |
| <h4>{t('Query')}</h4> |
| |
| <div style={{ marginBottom: 16 }}> |
| <ControlHeader |
| label={t('X-axis')} |
| description={t('Dimension to use on x-axis.')} |
| hovered |
| /> |
| <DndColumnSelect |
| value={formValues.x_axis ? [formValues.x_axis] : []} |
| onChange={(val: any) => |
| handleChange('x_axis')(Array.isArray(val) ? val[0] : val) |
| } |
| options={safeColumns} |
| name="x_axis" |
| label="" |
| multi={false} |
| canDelete |
| ghostButtonText={t('Time column')} |
| type="DndColumnSelect" |
| actions={actions} |
| /> |
| </div> |
| |
| <div style={{ marginBottom: 16 }}> |
| <ControlHeader |
| label={t('Metrics')} |
| description={t('One or many metrics to display')} |
| hovered |
| /> |
| <DndMetricSelect |
| value={formValues.metrics || []} |
| onChange={handleChange('metrics')} |
| datasource={datasource} |
| name="metrics" |
| label="" |
| multi |
| savedMetrics={safeMetrics} |
| /> |
| </div> |
| |
| <div style={{ marginBottom: 16 }}> |
| <ControlHeader |
| label={t('Group by')} |
| description={t('One or many columns to group by')} |
| hovered |
| /> |
| <DndColumnSelect |
| value={formValues.groupby || []} |
| onChange={handleChange('groupby')} |
| options={safeColumns} |
| name="groupby" |
| label="" |
| multi |
| canDelete |
| ghostButtonText={t('Add dimension')} |
| type="DndColumnSelect" |
| actions={actions} |
| /> |
| </div> |
| |
| <div style={{ marginBottom: 16 }}> |
| <ControlHeader |
| label={t('Filters')} |
| description={t('Filters to apply to the chart')} |
| hovered |
| /> |
| <DndFilterSelect |
| value={formValues.adhoc_filters || []} |
| onChange={handleChange('adhoc_filters')} |
| datasource={datasource} |
| columns={safeColumns} |
| formData={formValues} |
| name="adhoc_filters" |
| savedMetrics={safeMetrics} |
| selectedMetrics={formValues.metrics ? formValues.metrics : []} |
| type="DndFilterSelect" |
| actions={actions} |
| /> |
| </div> |
| |
| <div style={{ marginBottom: 16 }}> |
| <ControlHeader |
| label={t('Row limit')} |
| description={t('Limits the number of rows that get displayed.')} |
| hovered |
| /> |
| <TextControl |
| value={formValues.row_limit} |
| onChange={handleChange('row_limit')} |
| isInt |
| placeholder="10000" |
| controlId="row_limit" |
| /> |
| </div> |
| </div> |
| |
| {/* Time options */} |
| <div style={{ marginBottom: 24 }}> |
| <h4>{t('Time Options')}</h4> |
| |
| <div style={{ marginBottom: 16 }}> |
| <ControlHeader |
| label={t('Time grain')} |
| description={t('The time granularity for the visualization')} |
| hovered |
| /> |
| <SelectControl |
| value={formValues.time_grain_sqla} |
| onChange={handleChange('time_grain_sqla')} |
| choices={datasource?.time_grain_sqla_choices || []} |
| clearable={false} |
| renderTrigger |
| hovered |
| /> |
| </div> |
| |
| <div style={{ marginBottom: 16 }}> |
| <ControlHeader |
| label={t('Time range')} |
| description={t('The time range for the visualization')} |
| hovered |
| /> |
| <TextControl |
| value={formValues.time_range} |
| onChange={handleChange('time_range')} |
| placeholder="Last week" |
| controlId="time_range" |
| /> |
| </div> |
| </div> |
| </div> |
| ); |
| |
| const customizeTabContent = ( |
| <div> |
| {/* Chart Options */} |
| <div style={{ marginBottom: 24 }}> |
| <h4>{t('Chart Options')}</h4> |
| |
| <div style={{ marginBottom: 16 }}> |
| <ControlHeader |
| label={t('Color scheme')} |
| description={t('Color scheme for the chart')} |
| hovered |
| /> |
| {(() => { |
| const colorSchemeControl = ColorSchemeControl(); |
| const { hidden, ...cleanConfig } = colorSchemeControl.config || {}; |
| return ( |
| <Control |
| {...cleanConfig} |
| name="color_scheme" |
| value={formValues.color_scheme} |
| actions={{ |
| ...actions, |
| setControlValue: (field: string, val: any) => { |
| handleChange('color_scheme')(val); |
| }, |
| }} |
| renderTrigger |
| /> |
| ); |
| })()} |
| </div> |
| |
| <div style={{ marginBottom: 16 }}> |
| <ControlHeader |
| label={t('Time shift color')} |
| description={t('Color scheme for time shift comparison')} |
| hovered |
| /> |
| {(() => { |
| const timeShiftColorControl = TimeShiftColorControl(); |
| const { hidden, ...cleanConfig } = |
| timeShiftColorControl.config || {}; |
| return ( |
| <Control |
| {...cleanConfig} |
| name="time_shift_color" |
| value={formValues.time_shift_color} |
| actions={{ |
| ...actions, |
| setControlValue: (field: string, val: any) => { |
| handleChange('time_shift_color')(val); |
| }, |
| }} |
| renderTrigger |
| /> |
| ); |
| })()} |
| </div> |
| |
| <div style={{ marginBottom: 16 }}> |
| <SelectControl |
| label={t('Series Style')} |
| description={t('Series chart type (line, bar etc)')} |
| value={formValues.seriesType || EchartsTimeseriesSeriesType.Line} |
| onChange={handleChange('seriesType')} |
| choices={[ |
| [EchartsTimeseriesSeriesType.Line, t('Line')], |
| [EchartsTimeseriesSeriesType.Scatter, t('Scatter')], |
| [EchartsTimeseriesSeriesType.Smooth, t('Smooth Line')], |
| [EchartsTimeseriesSeriesType.Bar, t('Bar')], |
| [EchartsTimeseriesSeriesType.Start, t('Step - start')], |
| [EchartsTimeseriesSeriesType.Middle, t('Step - middle')], |
| [EchartsTimeseriesSeriesType.End, t('Step - end')], |
| ]} |
| clearable={false} |
| renderTrigger |
| hovered |
| /> |
| </div> |
| |
| <div style={{ marginBottom: 16 }}> |
| <CheckboxControl |
| label={t('Show Value')} |
| description={t('Show series values on the chart')} |
| value={formValues.show_value ?? false} |
| onChange={handleChange('show_value')} |
| renderTrigger |
| hovered |
| /> |
| </div> |
| |
| <div style={{ marginBottom: 16 }}> |
| <CheckboxControl |
| label={t('Area Chart')} |
| description={t( |
| 'Draw area under curves. Only applicable for line types.', |
| )} |
| value={formValues.area ?? false} |
| onChange={handleChange('area')} |
| renderTrigger |
| hovered |
| /> |
| </div> |
| |
| {formValues.area && ( |
| <div style={{ marginBottom: 16 }}> |
| <SliderControl |
| label={t('Area chart opacity')} |
| description={t( |
| 'Opacity of Area Chart. Also applies to confidence band.', |
| )} |
| value={formValues.opacity ?? 0.2} |
| onChange={handleChange('opacity')} |
| {...{ min: 0, max: 1, step: 0.1 }} |
| renderTrigger |
| /> |
| </div> |
| )} |
| |
| <div style={{ marginBottom: 16 }}> |
| <CheckboxControl |
| label={t('Marker')} |
| description={t( |
| 'Draw a marker on data points. Only applicable for line types.', |
| )} |
| value={formValues.markerEnabled ?? false} |
| onChange={handleChange('markerEnabled')} |
| renderTrigger |
| hovered |
| /> |
| </div> |
| |
| {formValues.markerEnabled && ( |
| <div style={{ marginBottom: 16 }}> |
| <SliderControl |
| label={t('Marker Size')} |
| description={t( |
| 'Size of marker. Also applies to forecast observations.', |
| )} |
| value={formValues.markerSize ?? 6} |
| onChange={handleChange('markerSize')} |
| {...{ min: 0, max: 20, step: 1 }} |
| renderTrigger |
| /> |
| </div> |
| )} |
| |
| <div style={{ marginBottom: 16 }}> |
| <CheckboxControl |
| label={t('Minor Ticks')} |
| description={t('Show minor ticks on axes')} |
| value={formValues.minorTicks ?? false} |
| onChange={handleChange('minorTicks')} |
| renderTrigger |
| hovered |
| /> |
| </div> |
| |
| <div style={{ marginBottom: 16 }}> |
| <CheckboxControl |
| label={t('Zoomable')} |
| description={t('Enable zooming')} |
| value={formValues.zoomable ?? false} |
| onChange={handleChange('zoomable')} |
| renderTrigger |
| hovered |
| /> |
| </div> |
| </div> |
| |
| {/* Legend */} |
| <div style={{ marginBottom: 24 }}> |
| <h4>{t('Legend')}</h4> |
| |
| <div style={{ marginBottom: 16 }}> |
| <CheckboxControl |
| label={t('Show legend')} |
| description={t('Whether to display a legend for the chart')} |
| value={formValues.show_legend ?? true} |
| onChange={handleChange('show_legend')} |
| renderTrigger |
| hovered |
| /> |
| </div> |
| |
| {formValues.show_legend && ( |
| <> |
| <div style={{ marginBottom: 16 }}> |
| <SelectControl |
| label={t('Type')} |
| description={t('Legend type')} |
| value={formValues.legendType || 'scroll'} |
| onChange={handleChange('legendType')} |
| choices={[ |
| ['scroll', t('Scroll')], |
| ['plain', t('Plain')], |
| ]} |
| clearable={false} |
| renderTrigger |
| hovered |
| /> |
| </div> |
| |
| <div style={{ marginBottom: 16 }}> |
| <SelectControl |
| label={t('Orientation')} |
| description={t('Legend Orientation')} |
| value={formValues.legendOrientation || 'top'} |
| onChange={handleChange('legendOrientation')} |
| choices={[ |
| ['top', t('Top')], |
| ['bottom', t('Bottom')], |
| ['left', t('Left')], |
| ['right', t('Right')], |
| ]} |
| clearable={false} |
| renderTrigger |
| hovered |
| /> |
| </div> |
| |
| <div style={{ marginBottom: 16 }}> |
| <TextControl |
| label={t('Margin')} |
| description={t('Additional padding for legend.')} |
| value={formValues.legendMargin} |
| onChange={handleChange('legendMargin')} |
| isInt |
| controlId="legendMargin" |
| /> |
| </div> |
| </> |
| )} |
| </div> |
| |
| {/* X Axis */} |
| <div style={{ marginBottom: 24 }}> |
| <h4>{t('X Axis')}</h4> |
| |
| <div style={{ marginBottom: 16 }}> |
| <SelectControl |
| label={t('Time format')} |
| description={`${D3_TIME_FORMAT_DOCS}. ${TIME_SERIES_DESCRIPTION_TEXT}`} |
| value={formValues.x_axis_time_format || 'smart_date'} |
| onChange={handleChange('x_axis_time_format')} |
| choices={D3_TIME_FORMAT_OPTIONS} |
| clearable={false} |
| renderTrigger |
| hovered |
| /> |
| </div> |
| |
| <div style={{ marginBottom: 16 }}> |
| <SliderControl |
| label={t('Label rotation')} |
| description={t('Rotation angle for axis labels')} |
| value={formValues.xAxisLabelRotation ?? 0} |
| onChange={handleChange('xAxisLabelRotation')} |
| {...{ min: -90, max: 90, step: 15 }} |
| renderTrigger |
| /> |
| </div> |
| |
| <div style={{ marginBottom: 16 }}> |
| <TextControl |
| label={t('Label interval')} |
| description={t('Interval for axis labels')} |
| value={formValues.xAxisLabelInterval} |
| onChange={handleChange('xAxisLabelInterval')} |
| controlId="xAxisLabelInterval" |
| /> |
| </div> |
| |
| <div style={{ marginBottom: 16 }}> |
| <CheckboxControl |
| label={t('Truncate X Axis')} |
| description={t( |
| 'Truncate X Axis. Can be overridden by specifying a min or max bound.', |
| )} |
| value={formValues.truncateXAxis ?? true} |
| onChange={handleChange('truncateXAxis')} |
| renderTrigger |
| hovered |
| /> |
| </div> |
| |
| {formValues.truncateXAxis && ( |
| <div style={{ marginBottom: 16 }}> |
| <ControlHeader |
| label={t('X Axis Bounds')} |
| description={t( |
| 'Bounds for the X-axis. When left empty, the bounds are dynamically defined based on the min/max of the data.', |
| )} |
| hovered |
| /> |
| <div style={{ display: 'flex', gap: 8 }}> |
| <TextControl |
| value={formValues.x_axis_bounds?.[0]} |
| onChange={(val: any) => { |
| const bounds = formValues.x_axis_bounds || [null, null]; |
| handleChange('x_axis_bounds')([val, bounds[1]]); |
| }} |
| placeholder="Min" |
| controlId="x_axis_bounds_min" |
| /> |
| <TextControl |
| value={formValues.x_axis_bounds?.[1]} |
| onChange={(val: any) => { |
| const bounds = formValues.x_axis_bounds || [null, null]; |
| handleChange('x_axis_bounds')([bounds[0], val]); |
| }} |
| placeholder="Max" |
| controlId="x_axis_bounds_max" |
| /> |
| </div> |
| </div> |
| )} |
| </div> |
| |
| {/* Tooltip */} |
| <div style={{ marginBottom: 24 }}> |
| <h4>{t('Tooltip')}</h4> |
| |
| <div style={{ marginBottom: 16 }}> |
| <CheckboxControl |
| label={t('Rich tooltip')} |
| description={t( |
| 'Shows a list of all series available at that point in time', |
| )} |
| value={formValues.rich_tooltip ?? true} |
| onChange={handleChange('rich_tooltip')} |
| renderTrigger |
| hovered |
| /> |
| </div> |
| |
| <div style={{ marginBottom: 16 }}> |
| <SelectControl |
| label={t('Tooltip time format')} |
| description={t('Time format for tooltip')} |
| value={formValues.tooltipTimeFormat || 'smart_date'} |
| onChange={handleChange('tooltipTimeFormat')} |
| choices={D3_TIME_FORMAT_OPTIONS} |
| clearable={false} |
| renderTrigger |
| hovered |
| /> |
| </div> |
| |
| <div style={{ marginBottom: 16 }}> |
| <CheckboxControl |
| label={t('Show total')} |
| description={t('Display total value in tooltip')} |
| value={formValues.showTooltipTotal ?? false} |
| onChange={handleChange('showTooltipTotal')} |
| renderTrigger |
| hovered |
| /> |
| </div> |
| |
| <div style={{ marginBottom: 16 }}> |
| <CheckboxControl |
| label={t('Show percentage')} |
| description={t('Show percentage in tooltip')} |
| value={formValues.showTooltipPercentage ?? false} |
| onChange={handleChange('showTooltipPercentage')} |
| renderTrigger |
| hovered |
| /> |
| </div> |
| </div> |
| |
| {/* Y Axis */} |
| <div style={{ marginBottom: 24 }}> |
| <h4>{t('Y Axis')}</h4> |
| |
| <div style={{ marginBottom: 16 }}> |
| <ControlHeader |
| label={t('Y axis format')} |
| description={t('Format for Y axis values')} |
| hovered |
| /> |
| {(() => { |
| const yAxisFormatControl = YAxisFormatControl(); |
| const { hidden, ...cleanConfig } = yAxisFormatControl.config || {}; |
| return ( |
| <Control |
| {...cleanConfig} |
| name="y_axis_format" |
| value={formValues.y_axis_format} |
| actions={{ |
| ...actions, |
| setControlValue: (field: string, val: any) => { |
| handleChange('y_axis_format')(val); |
| }, |
| }} |
| renderTrigger |
| /> |
| ); |
| })()} |
| </div> |
| |
| <div style={{ marginBottom: 16 }}> |
| <ControlHeader |
| label={t('Currency format')} |
| description={t('Format for currency values')} |
| hovered |
| /> |
| {(() => { |
| const currencyFormatControl = CurrencyFormatControl(); |
| const { hidden, ...cleanConfig } = |
| currencyFormatControl.config || {}; |
| return ( |
| <Control |
| {...cleanConfig} |
| name="currency_format" |
| value={formValues.currency_format} |
| actions={{ |
| ...actions, |
| setControlValue: (field: string, val: any) => { |
| handleChange('currency_format')(val); |
| }, |
| }} |
| renderTrigger |
| /> |
| ); |
| })()} |
| </div> |
| |
| <div style={{ marginBottom: 16 }}> |
| <CheckboxControl |
| label={t('Logarithmic y-axis')} |
| description={t('Logarithmic y-axis')} |
| value={formValues.logAxis ?? false} |
| onChange={handleChange('logAxis')} |
| renderTrigger |
| hovered |
| /> |
| </div> |
| |
| <div style={{ marginBottom: 16 }}> |
| <CheckboxControl |
| label={t('Minor Split Line')} |
| description={t('Draw split lines for minor y-axis ticks')} |
| value={formValues.minorSplitLine ?? false} |
| onChange={handleChange('minorSplitLine')} |
| renderTrigger |
| hovered |
| /> |
| </div> |
| |
| <div style={{ marginBottom: 16 }}> |
| <CheckboxControl |
| label={t('Truncate Y Axis')} |
| description={t( |
| 'Truncate Y Axis. Can be overridden by specifying a min or max bound.', |
| )} |
| value={formValues.truncateYAxis ?? false} |
| onChange={handleChange('truncateYAxis')} |
| renderTrigger |
| hovered |
| /> |
| </div> |
| |
| {formValues.truncateYAxis && ( |
| <div style={{ marginBottom: 16 }}> |
| <ControlHeader |
| label={t('Y Axis Bounds')} |
| description={t( |
| 'Bounds for the Y-axis. When left empty, the bounds are dynamically defined based on the min/max of the data.', |
| )} |
| hovered |
| /> |
| <div style={{ display: 'flex', gap: 8 }}> |
| <TextControl |
| value={formValues.y_axis_bounds?.[0]} |
| onChange={(val: any) => { |
| const bounds = formValues.y_axis_bounds || [null, null]; |
| handleChange('y_axis_bounds')([val, bounds[1]]); |
| }} |
| placeholder="Min" |
| controlId="y_axis_bounds_min" |
| /> |
| <TextControl |
| value={formValues.y_axis_bounds?.[1]} |
| onChange={(val: any) => { |
| const bounds = formValues.y_axis_bounds || [null, null]; |
| handleChange('y_axis_bounds')([bounds[0], val]); |
| }} |
| placeholder="Max" |
| controlId="y_axis_bounds_max" |
| /> |
| </div> |
| </div> |
| )} |
| </div> |
| </div> |
| ); |
| |
| const tabItems = [ |
| { key: 'data', label: t('Data'), children: dataTabContent }, |
| { key: 'customize', label: t('Customize'), children: customizeTabContent }, |
| ]; |
| |
| return ( |
| <div style={{ padding: '16px' }}> |
| <Tabs |
| activeKey={activeTab} |
| onChange={setActiveTab} |
| items={tabItems} |
| size="large" |
| /> |
| </div> |
| ); |
| }; |
| |
| // CRITICAL: Mark as modern panel |
| (LineControlPanel as any).isModernPanel = true; |
| |
| // Export wrapper config for compatibility |
| const config = { |
| controlPanelSections: [ |
| { |
| label: null, |
| expanded: true, |
| controlSetRows: [[LineControlPanel as any]], |
| }, |
| ], |
| controlOverrides: { |
| row_limit: { |
| default: 10000, |
| label: t('Row limit'), |
| renderTrigger: true, |
| }, |
| area: { |
| default: false, |
| label: t('Area Chart'), |
| renderTrigger: true, |
| }, |
| opacity: { |
| default: 0.2, |
| label: t('Area chart opacity'), |
| renderTrigger: true, |
| }, |
| markerEnabled: { |
| default: false, |
| label: t('Marker'), |
| renderTrigger: true, |
| }, |
| markerSize: { |
| default: 6, |
| label: t('Marker Size'), |
| renderTrigger: true, |
| }, |
| seriesType: { |
| default: EchartsTimeseriesSeriesType.Line, |
| label: t('Series Style'), |
| renderTrigger: true, |
| }, |
| show_value: { |
| default: false, |
| label: t('Show Value'), |
| renderTrigger: true, |
| }, |
| logAxis: { |
| default: false, |
| label: t('Logarithmic y-axis'), |
| renderTrigger: true, |
| }, |
| minorSplitLine: { |
| default: false, |
| label: t('Minor Split Line'), |
| renderTrigger: true, |
| }, |
| truncateXAxis: { |
| default: true, |
| label: t('Truncate X Axis'), |
| renderTrigger: true, |
| }, |
| truncateYAxis: { |
| default: false, |
| label: t('Truncate Y Axis'), |
| renderTrigger: true, |
| }, |
| x_axis_time_format: { |
| default: 'smart_date', |
| label: t('Time format'), |
| renderTrigger: true, |
| }, |
| rich_tooltip: { |
| default: true, |
| label: t('Rich tooltip'), |
| renderTrigger: true, |
| }, |
| tooltipTimeFormat: { |
| default: 'smart_date', |
| label: t('Tooltip time format'), |
| renderTrigger: true, |
| }, |
| zoomable: { |
| default: false, |
| label: t('Zoomable'), |
| renderTrigger: true, |
| }, |
| show_legend: { |
| default: true, |
| label: t('Show legend'), |
| renderTrigger: true, |
| }, |
| legendType: { |
| default: 'scroll', |
| label: t('Legend type'), |
| renderTrigger: true, |
| }, |
| legendOrientation: { |
| default: 'top', |
| label: t('Legend orientation'), |
| renderTrigger: true, |
| }, |
| minorTicks: { |
| default: false, |
| label: t('Minor Ticks'), |
| renderTrigger: true, |
| }, |
| }, |
| }; |
| |
| export default config; |