diff --git a/app/client/src/components/designSystems/appsmith/ChartComponent.tsx b/app/client/src/components/designSystems/appsmith/ChartComponent.tsx index 18f2537cff9f..6ecdbb2b3f5a 100644 --- a/app/client/src/components/designSystems/appsmith/ChartComponent.tsx +++ b/app/client/src/components/designSystems/appsmith/ChartComponent.tsx @@ -2,10 +2,14 @@ import _, { get } from "lodash"; import React from "react"; import styled from "styled-components"; -import { getBorderCSSShorthand, invisible } from "constants/DefaultTheme"; -import { getAppsmithConfigs } from "configs"; -import { AllChartData, ChartDataPoint, ChartType } from "widgets/ChartWidget"; import log from "loglevel"; +import { AllChartData, ChartDataPoint, ChartType } from "widgets/ChartWidget"; +import { getAppsmithConfigs } from "configs"; +import { getBorderCSSShorthand, invisible } from "constants/DefaultTheme"; +import { + LabelOrientation, + LABEL_ORIENTATION_COMPATIBLE_CHARTS, +} from "constants/ChartConstants"; export interface CustomFusionChartConfig { type: string; @@ -48,16 +52,17 @@ FusionCharts.options.license({ }); export interface ChartComponentProps { - chartType: ChartType; + allowHorizontalScroll: boolean; chartData: AllChartData; - customFusionChartConfig: CustomFusionChartConfig; - xAxisName: string; - yAxisName: string; chartName: string; - widgetId: string; + chartType: ChartType; + customFusionChartConfig: CustomFusionChartConfig; isVisible?: boolean; - allowHorizontalScroll: boolean; + labelOrientation?: LabelOrientation; onDataPointClick: (selectedDataPoint: ChartSelectedDataPoint) => void; + widgetId: string; + xAxisName: string; + yAxisName: string; } const CanvasContainer = styled.div< @@ -74,9 +79,14 @@ const CanvasContainer = styled.div< padding: 10px 0 0 0; }`; +export const isLabelOrientationApplicableFor = (chartType: string) => + LABEL_ORIENTATION_COMPATIBLE_CHARTS.includes(chartType); + class ChartComponent extends React.Component { chartInstance = new FusionCharts(); + chartContainerId = this.props.widgetId + "chart-container"; + getChartType = () => { const { allowHorizontalScroll, chartData, chartType } = this.props; const dataLength = Object.keys(chartData).length; @@ -151,7 +161,7 @@ class ChartComponent extends React.Component { }); }; - getChartCategoriesMutliSeries = (chartData: AllChartData) => { + getChartCategoriesMultiSeries = (chartData: AllChartData) => { const categories: string[] = []; Object.keys(chartData).forEach((key: string) => { @@ -173,7 +183,7 @@ class ChartComponent extends React.Component { }; getChartCategories = (chartData: AllChartData) => { - const categories: string[] = this.getChartCategoriesMutliSeries(chartData); + const categories: string[] = this.getChartCategoriesMultiSeries(chartData); if (categories.length === 0) { return [ @@ -218,7 +228,7 @@ class ChartComponent extends React.Component { * @returns */ getChartDataset = (chartData: AllChartData) => { - const categories: string[] = this.getChartCategoriesMutliSeries(chartData); + const categories: string[] = this.getChartCategoriesMultiSeries(chartData); const dataset = Object.keys(chartData).map((key: string) => { const item = get(chartData, `${key}`); @@ -236,8 +246,32 @@ class ChartComponent extends React.Component { return dataset; }; + getLabelOrientationConfig = () => { + switch (this.props.labelOrientation) { + case LabelOrientation.AUTO: + return {}; + case LabelOrientation.ROTATE: + return { + labelDisplay: "rotate", + slantLabel: "0", + }; + case LabelOrientation.SLANT: + return { + labelDisplay: "rotate", + slantLabel: "1", + }; + case LabelOrientation.STAGGER: + return { + labelDisplay: "stagger", + }; + default: { + return {}; + } + } + }; + getChartConfig = () => { - return { + let config = { caption: this.props.chartName, xAxisName: this.props.xAxisName, yAxisName: this.props.yAxisName, @@ -246,6 +280,15 @@ class ChartComponent extends React.Component { captionHorizontalPadding: 10, alignCaptionWithCanvas: 0, }; + + if (isLabelOrientationApplicableFor(this.props.chartType)) { + config = { + ...config, + ...this.getLabelOrientationConfig(), + }; + } + + return config; }; getDatalength = () => { @@ -338,7 +381,7 @@ class ChartComponent extends React.Component { createGraph = () => { if (this.props.chartType === "CUSTOM_FUSION_CHART") { const chartConfig = { - renderAt: this.props.widgetId + "chart-container", + renderAt: this.chartContainerId, width: "100%", height: "100%", events: { @@ -364,7 +407,7 @@ class ChartComponent extends React.Component { const chartConfig = { type: this.getChartType(), - renderAt: this.props.widgetId + "chart-container", + renderAt: this.chartContainerId, width: "100%", height: "100%", dataFormat: "json", @@ -410,7 +453,7 @@ class ChartComponent extends React.Component { if (!_.isEqual(prevProps, this.props)) { if (this.props.chartType === "CUSTOM_FUSION_CHART") { const chartConfig = { - renderAt: this.props.widgetId + "chart-container", + renderAt: this.chartContainerId, width: "100%", height: "100%", events: { @@ -446,9 +489,7 @@ class ChartComponent extends React.Component { render() { //eslint-disable-next-line @typescript-eslint/no-unused-vars const { onDataPointClick, ...rest } = this.props; - return ( - - ); + return ; } } diff --git a/app/client/src/constants/CustomChartConstants.ts b/app/client/src/constants/ChartConstants.ts similarity index 89% rename from app/client/src/constants/CustomChartConstants.ts rename to app/client/src/constants/ChartConstants.ts index 6b51653a5911..2e68e4283317 100644 --- a/app/client/src/constants/CustomChartConstants.ts +++ b/app/client/src/constants/ChartConstants.ts @@ -116,3 +116,16 @@ export const CUSTOM_CHART_DEFAULT_PARSED = { data: [], }, }; + +export enum LabelOrientation { + AUTO = "auto", + SLANT = "slant", + ROTATE = "rotate", + STAGGER = "stagger", +} + +export const LABEL_ORIENTATION_COMPATIBLE_CHARTS = [ + "LINE_CHART", + "AREA_CHART", + "COLUMN_CHART", +]; diff --git a/app/client/src/mockResponses/WidgetConfigResponse.tsx b/app/client/src/mockResponses/WidgetConfigResponse.tsx index b110f849033b..734a11c13cbf 100644 --- a/app/client/src/mockResponses/WidgetConfigResponse.tsx +++ b/app/client/src/mockResponses/WidgetConfigResponse.tsx @@ -20,6 +20,7 @@ import { ButtonStyleTypes, ButtonVariantTypes, } from "components/designSystems/appsmith/IconButtonComponent"; +import { LabelOrientation } from "constants/ChartConstants"; /* ********************************{Grid Density Migration}********************************* @@ -655,6 +656,7 @@ const WidgetConfigResponse: WidgetConfigReducerState = { }, xAxisName: "Last Week", yAxisName: "Total Order Revenue $", + labelOrientation: LabelOrientation.AUTO, customFusionChartConfig: { type: "column2d", dataSource: { diff --git a/app/client/src/widgets/ChartWidget/index.tsx b/app/client/src/widgets/ChartWidget/index.tsx index a28730fd4ae4..7862b0c2279b 100644 --- a/app/client/src/widgets/ChartWidget/index.tsx +++ b/app/client/src/widgets/ChartWidget/index.tsx @@ -1,16 +1,17 @@ +import * as Sentry from "@sentry/react"; import React, { lazy, Suspense } from "react"; + import BaseWidget, { WidgetProps, WidgetState } from "widgets/BaseWidget"; -import { WidgetType } from "constants/WidgetConstants"; +import propertyConfig from "widgets/ChartWidget/propertyConfig"; import Skeleton from "components/utils/Skeleton"; -import * as Sentry from "@sentry/react"; -import { retryPromise } from "utils/AppsmithUtils"; -import { EventType } from "constants/AppsmithActionConstants/ActionConstants"; import withMeta, { WithMeta } from "widgets/MetaHOC"; -import propertyConfig from "widgets/ChartWidget/propertyConfig"; import { - CustomFusionChartConfig, + ChartComponentProps, ChartSelectedDataPoint, } from "components/designSystems/appsmith/ChartComponent"; +import { EventType } from "constants/AppsmithActionConstants/ActionConstants"; +import { retryPromise } from "utils/AppsmithUtils"; +import { WidgetType } from "constants/WidgetConstants"; const ChartComponent = lazy(() => retryPromise(() => @@ -56,6 +57,7 @@ class ChartWidget extends BaseWidget { customFusionChartConfig={this.props.customFusionChartConfig} isVisible={this.props.isVisible} key={this.props.widgetId} + labelOrientation={this.props.labelOrientation} onDataPointClick={this.onDataPointClick} widgetId={this.props.widgetId} xAxisName={this.props.xAxisName} @@ -92,17 +94,12 @@ export interface ChartData { data: ChartDataPoint[]; } -export interface ChartWidgetProps extends WidgetProps, WithMeta { - chartType: ChartType; - chartData: AllChartData; - customFusionChartConfig: CustomFusionChartConfig; - xAxisName: string; - yAxisName: string; - chartName: string; - isVisible?: boolean; - allowHorizontalScroll: boolean; +type ChartComponentPartialProps = Omit; +export interface ChartWidgetProps + extends WidgetProps, + WithMeta, + ChartComponentPartialProps { onDataPointClick?: string; - selectedDataPoint?: ChartDataPoint; } export default ChartWidget; diff --git a/app/client/src/widgets/ChartWidget/propertyConfig.test.ts b/app/client/src/widgets/ChartWidget/propertyConfig.test.ts index 124515f6b3e0..c2870d59823e 100644 --- a/app/client/src/widgets/ChartWidget/propertyConfig.test.ts +++ b/app/client/src/widgets/ChartWidget/propertyConfig.test.ts @@ -1,6 +1,8 @@ /* eslint-disable @typescript-eslint/no-namespace */ import { isString, get } from "lodash"; + import config from "./propertyConfig"; +import { PropertyPaneControlConfig } from "constants/PropertyControlConstants"; declare global { namespace jest { @@ -87,4 +89,18 @@ describe("Validate Chart Widget's property config", () => { expect(result).toBeTruthy(); }); }); + + it("Validates that axis labelOrientation is visible when chartType are LINE_CHART AREA_CHART COLUMN_CHART", () => { + const allowedChartsTypes = ["LINE_CHART", "AREA_CHART", "COLUMN_CHART"]; + + const axisSection = config.find((c) => c.sectionName === "Axis"); + const labelOrientationProperty = ((axisSection?.children as unknown) as PropertyPaneControlConfig[]).find( + (p) => p.propertyName === "labelOrientation", + ); + + allowedChartsTypes.forEach((chartType) => { + const result = labelOrientationProperty?.hidden?.({ chartType }, ""); + expect(result).toBeFalsy(); + }); + }); }); diff --git a/app/client/src/widgets/ChartWidget/propertyConfig.ts b/app/client/src/widgets/ChartWidget/propertyConfig.ts index ea194231cb91..8c94d1825b06 100644 --- a/app/client/src/widgets/ChartWidget/propertyConfig.ts +++ b/app/client/src/widgets/ChartWidget/propertyConfig.ts @@ -1,7 +1,8 @@ import { ChartWidgetProps } from "widgets/ChartWidget"; import { ValidationTypes } from "constants/WidgetValidation"; import { EvaluationSubstitutionType } from "entities/DataTree/dataTreeFactory"; -import { CUSTOM_CHART_TYPES } from "constants/CustomChartConstants"; +import { CUSTOM_CHART_TYPES, LabelOrientation } from "constants/ChartConstants"; +import { isLabelOrientationApplicableFor } from "components/designSystems/appsmith/ChartComponent"; export default [ { @@ -244,9 +245,38 @@ export default [ controlType: "SWITCH", isBindProperty: false, isTriggerProperty: false, - hidden: (x: any) => x.chartType === "CUSTOM_FUSION_CHART", + hidden: (x: ChartWidgetProps) => x.chartType === "CUSTOM_FUSION_CHART", dependencies: ["chartType"], }, + { + helpText: "Changes the x-axis label orientation", + propertyName: "labelOrientation", + label: "x-axis Label Orientation", + hidden: (x: ChartWidgetProps) => + !isLabelOrientationApplicableFor(x.chartType), + isBindProperty: false, + isTriggerProperty: false, + dependencies: ["chartType"], + controlType: "DROP_DOWN", + options: [ + { + label: "Auto", + value: LabelOrientation.AUTO, + }, + { + label: "Slant", + value: LabelOrientation.SLANT, + }, + { + label: "Rotate", + value: LabelOrientation.ROTATE, + }, + { + label: "Stagger", + value: LabelOrientation.STAGGER, + }, + ], + }, ], }, {