diff --git a/app/charts/area/areas-state.tsx b/app/charts/area/areas-state.tsx index de9cc6fbd..838adf7c0 100644 --- a/app/charts/area/areas-state.tsx +++ b/app/charts/area/areas-state.tsx @@ -23,7 +23,7 @@ import { useAreasStateVariables, } from "@/charts/area/areas-state-props"; import { - getChartBounds, + useChartBounds, useChartPadding, } from "@/charts/shared/chart-dimensions"; import { @@ -336,7 +336,7 @@ const useAreasState = ( bottom, left, }; - const bounds = getChartBounds(width, margins, height); + const bounds = useChartBounds(width, margins, height); const { chartWidth, chartHeight } = bounds; /** Adjust scales according to dimensions */ diff --git a/app/charts/area/chart-area.tsx b/app/charts/area/chart-area.tsx index ddd2b7891..4d979503f 100644 --- a/app/charts/area/chart-area.tsx +++ b/app/charts/area/chart-area.tsx @@ -31,7 +31,7 @@ const ChartAreas = memo((props: ChartProps) => { return ( - + diff --git a/app/charts/column/chart-column.tsx b/app/charts/column/chart-column.tsx index c3e261a50..eef1ae59e 100644 --- a/app/charts/column/chart-column.tsx +++ b/app/charts/column/chart-column.tsx @@ -49,7 +49,7 @@ const ChartColumns = memo((props: ChartProps) => { <> {fields.segment?.componentIri && fields.segment.type === "stacked" ? ( - + @@ -77,7 +77,7 @@ const ChartColumns = memo((props: ChartProps) => { ) : fields.segment?.componentIri && fields.segment.type === "grouped" ? ( - + @@ -108,7 +108,7 @@ const ChartColumns = memo((props: ChartProps) => { ) : ( - + diff --git a/app/charts/column/columns-grouped-state.tsx b/app/charts/column/columns-grouped-state.tsx index 2f72445ec..fdf951691 100644 --- a/app/charts/column/columns-grouped-state.tsx +++ b/app/charts/column/columns-grouped-state.tsx @@ -23,7 +23,7 @@ import { PADDING_WITHIN, } from "@/charts/column/constants"; import { - getChartBounds, + useChartBounds, useChartPadding, } from "@/charts/shared/chart-dimensions"; import { @@ -339,7 +339,7 @@ const useColumnsGroupedState = ( bottom, left, }; - const bounds = getChartBounds(width, margins, height); + const bounds = useChartBounds(width, margins, height); const { chartWidth, chartHeight } = bounds; // Adjust of scales based on chart dimensions diff --git a/app/charts/column/columns-stacked-state.tsx b/app/charts/column/columns-stacked-state.tsx index ce3b6186f..256404024 100644 --- a/app/charts/column/columns-stacked-state.tsx +++ b/app/charts/column/columns-stacked-state.tsx @@ -27,7 +27,7 @@ import { } from "@/charts/column/columns-stacked-state-props"; import { PADDING_INNER, PADDING_OUTER } from "@/charts/column/constants"; import { - getChartBounds, + useChartBounds, useChartPadding, } from "@/charts/shared/chart-dimensions"; import { @@ -397,7 +397,7 @@ const useColumnsStackedState = ( bottom, left, }; - const bounds = getChartBounds(width, margins, height); + const bounds = useChartBounds(width, margins, height); const { chartWidth, chartHeight } = bounds; xScale.range([0, chartWidth]); diff --git a/app/charts/column/columns-state.tsx b/app/charts/column/columns-state.tsx index b2989ebca..ecb5eb592 100644 --- a/app/charts/column/columns-state.tsx +++ b/app/charts/column/columns-state.tsx @@ -16,7 +16,7 @@ import { } from "@/charts/column/columns-state-props"; import { PADDING_INNER, PADDING_OUTER } from "@/charts/column/constants"; import { - getChartBounds, + useChartBounds, useChartPadding, } from "@/charts/shared/chart-dimensions"; import { @@ -199,7 +199,7 @@ const useColumnsState = ( left, }; - const bounds = getChartBounds(width, margins, height); + const bounds = useChartBounds(width, margins, height); const { chartWidth, chartHeight } = bounds; xScale.range([0, chartWidth]); diff --git a/app/charts/combo/chart-combo-line-column.tsx b/app/charts/combo/chart-combo-line-column.tsx index f6b8d5c99..bd701e012 100644 --- a/app/charts/combo/chart-combo-line-column.tsx +++ b/app/charts/combo/chart-combo-line-column.tsx @@ -29,7 +29,7 @@ const ChartComboLineColumn = memo( const dashboardFilters = useDashboardInteractiveFilters(); return ( - + diff --git a/app/charts/combo/chart-combo-line-dual.tsx b/app/charts/combo/chart-combo-line-dual.tsx index f8245b0bf..1203f2fb2 100644 --- a/app/charts/combo/chart-combo-line-dual.tsx +++ b/app/charts/combo/chart-combo-line-dual.tsx @@ -28,7 +28,7 @@ const ChartComboLineDual = memo((props: ChartProps) => { const dashboardFilters = useDashboardInteractiveFilters(); return ( - + diff --git a/app/charts/combo/chart-combo-line-single.tsx b/app/charts/combo/chart-combo-line-single.tsx index 1ce183be3..19c53722e 100644 --- a/app/charts/combo/chart-combo-line-single.tsx +++ b/app/charts/combo/chart-combo-line-single.tsx @@ -29,7 +29,7 @@ const ChartComboLineSingle = memo( const dashboardFilters = useDashboardInteractiveFilters(); return ( - + diff --git a/app/charts/combo/combo-line-column-state.tsx b/app/charts/combo/combo-line-column-state.tsx index c277caeb9..31fae5b83 100644 --- a/app/charts/combo/combo-line-column-state.tsx +++ b/app/charts/combo/combo-line-column-state.tsx @@ -17,7 +17,7 @@ import { } from "@/charts/combo/combo-state"; import { TICK_PADDING } from "@/charts/shared/axis-height-linear"; import { - getChartBounds, + useChartBounds, useChartPadding, } from "@/charts/shared/chart-dimensions"; import { @@ -145,7 +145,7 @@ const useComboLineColumnState = ( ); const right = Math.max(maxRightTickWidth, 40); const margins = getMargins({ left, right, bottom }); - const bounds = getChartBounds(width, margins, height); + const bounds = useChartBounds(width, margins, height); const { chartWidth, chartHeight } = bounds; const xScales = [xScale, xScaleTime, xScaleTimeRange]; const yScales = [yScale, yScaleLeft, yScaleRight]; diff --git a/app/charts/combo/combo-line-dual-state.tsx b/app/charts/combo/combo-line-dual-state.tsx index 932ed99d5..44c1e483f 100644 --- a/app/charts/combo/combo-line-dual-state.tsx +++ b/app/charts/combo/combo-line-dual-state.tsx @@ -16,7 +16,7 @@ import { } from "@/charts/combo/combo-state"; import { TICK_PADDING } from "@/charts/shared/axis-height-linear"; import { - getChartBounds, + useChartBounds, useChartPadding, } from "@/charts/shared/chart-dimensions"; import { @@ -128,7 +128,7 @@ const useComboLineDualState = ( ); const right = Math.max(maxRightTickWidth, 40); const margins = getMargins({ left, right, bottom }); - const bounds = getChartBounds(width, margins, height); + const bounds = useChartBounds(width, margins, height); const { chartWidth, chartHeight } = bounds; const xScales = [xScale, xScaleTimeRange]; const yScales = [yScale, yScaleLeft, yScaleRight]; diff --git a/app/charts/combo/combo-line-single-state.tsx b/app/charts/combo/combo-line-single-state.tsx index 5ebabcde2..7b5552b19 100644 --- a/app/charts/combo/combo-line-single-state.tsx +++ b/app/charts/combo/combo-line-single-state.tsx @@ -14,7 +14,7 @@ import { useYScales, } from "@/charts/combo/combo-state"; import { - getChartBounds, + useChartBounds, useChartPadding, } from "@/charts/shared/chart-dimensions"; import { @@ -109,7 +109,7 @@ const useComboLineSingleState = ( formatNumber, }); const margins = getMargins({ left, bottom }); - const bounds = getChartBounds(width, margins, height); + const bounds = useChartBounds(width, margins, height); const { chartWidth, chartHeight } = bounds; const xScales = [xScale, xScaleTimeRange]; const yScales = [yScale]; diff --git a/app/charts/line/chart-lines.tsx b/app/charts/line/chart-lines.tsx index 712fd94df..308b1a484 100644 --- a/app/charts/line/chart-lines.tsx +++ b/app/charts/line/chart-lines.tsx @@ -33,7 +33,7 @@ const ChartLines = memo((props: ChartProps) => { const dashboardFilters = useDashboardInteractiveFilters(); return ( - + diff --git a/app/charts/line/lines-state.tsx b/app/charts/line/lines-state.tsx index 938b94177..d0bb1d29e 100644 --- a/app/charts/line/lines-state.tsx +++ b/app/charts/line/lines-state.tsx @@ -17,7 +17,7 @@ import { useLinesStateVariables, } from "@/charts/line/lines-state-props"; import { - getChartBounds, + useChartBounds, useChartPadding, } from "@/charts/shared/chart-dimensions"; import { getWideData } from "@/charts/shared/chart-helpers"; @@ -228,7 +228,7 @@ const useLinesState = ( bottom, left, }; - const bounds = getChartBounds(width, margins, height); + const bounds = useChartBounds(width, margins, height); const { chartWidth, chartHeight } = bounds; xScale.range([0, chartWidth]); diff --git a/app/charts/map/chart-map.tsx b/app/charts/map/chart-map.tsx index 62bba7f41..b525f15eb 100644 --- a/app/charts/map/chart-map.tsx +++ b/app/charts/map/chart-map.tsx @@ -136,7 +136,7 @@ const ChartMap = memo((props: ChartMapProps) => { const filters = useChartConfigFilters(chartConfig); return ( - + diff --git a/app/charts/pie/chart-pie.tsx b/app/charts/pie/chart-pie.tsx index a5139abd3..d7311c3c3 100644 --- a/app/charts/pie/chart-pie.tsx +++ b/app/charts/pie/chart-pie.tsx @@ -34,7 +34,7 @@ const ChartPie = memo((props: ChartProps) => { return ( - + diff --git a/app/charts/pie/pie-state.tsx b/app/charts/pie/pie-state.tsx index 33c5ebc04..3b3aef7a3 100644 --- a/app/charts/pie/pie-state.tsx +++ b/app/charts/pie/pie-state.tsx @@ -10,7 +10,7 @@ import { usePieStateData, usePieStateVariables, } from "@/charts/pie/pie-state-props"; -import { getChartBounds } from "@/charts/shared/chart-dimensions"; +import { useChartBounds } from "@/charts/shared/chart-dimensions"; import { ChartContext, ChartStateData, @@ -147,7 +147,7 @@ const usePieState = ( bottom: 40, left: 40, }; - const bounds = getChartBounds(width, margins, height); + const bounds = useChartBounds(width, margins, height); const { chartWidth, chartHeight } = bounds; // Pie values diff --git a/app/charts/scatterplot/chart-scatterplot.tsx b/app/charts/scatterplot/chart-scatterplot.tsx index d5d8df850..a16f87f7d 100644 --- a/app/charts/scatterplot/chart-scatterplot.tsx +++ b/app/charts/scatterplot/chart-scatterplot.tsx @@ -36,7 +36,7 @@ const ChartScatterplot = memo((props: ChartProps) => { const filters = useChartConfigFilters(chartConfig); return ( - + diff --git a/app/charts/scatterplot/scatterplot-state.tsx b/app/charts/scatterplot/scatterplot-state.tsx index a81ed5b96..5df09d849 100644 --- a/app/charts/scatterplot/scatterplot-state.tsx +++ b/app/charts/scatterplot/scatterplot-state.tsx @@ -10,7 +10,7 @@ import { useScatterplotStateVariables, } from "@/charts/scatterplot//scatterplot-state-props"; import { - getChartBounds, + useChartBounds, useChartPadding, } from "@/charts/shared/chart-dimensions"; import { @@ -158,7 +158,7 @@ const useScatterplotState = ( bottom, left, }; - const bounds = getChartBounds(width, margins, height); + const bounds = useChartBounds(width, margins, height); const { chartWidth, chartHeight } = bounds; xScale.range([0, chartWidth]); diff --git a/app/charts/shared/chart-dimensions.tsx b/app/charts/shared/chart-dimensions.tsx index 900d6c28e..206c1653b 100644 --- a/app/charts/shared/chart-dimensions.tsx +++ b/app/charts/shared/chart-dimensions.tsx @@ -6,7 +6,13 @@ import { BRUSH_BOTTOM_SPACE } from "@/charts/shared/brush/constants"; import { getTickNumber } from "@/charts/shared/ticks"; import { TICK_FONT_SIZE } from "@/charts/shared/use-chart-theme"; import { Bounds, Margins } from "@/charts/shared/use-size"; -import { ChartConfig } from "@/configurator"; +import { CHART_GRID_MIN_HEIGHT } from "@/components/react-grid"; +import { + ChartConfig, + hasChartConfigs, + isLayoutingFreeCanvas, + useConfiguratorState, +} from "@/configurator"; import { useDashboardInteractiveFilters } from "@/stores/interactive-filters"; import { getTextWidth } from "@/utils/get-text-width"; @@ -107,20 +113,25 @@ export const useChartPadding = (props: ComputeChartPaddingProps) => { ]); }; -export const getChartBounds = ( +const ASPECT_RATIO = 2 / 5; + +export const useChartBounds = ( width: number, margins: Margins, height: number ): Bounds => { + const [state] = useConfiguratorState(hasChartConfigs); const { left, top, right, bottom } = margins; const chartWidth = width - left - right; - const chartHeight = height - top - bottom; + const chartHeight = isLayoutingFreeCanvas(state) + ? Math.max(CHART_GRID_MIN_HEIGHT, height - top - bottom) + : chartWidth * ASPECT_RATIO; return { width, height: chartHeight + top + bottom, - aspectRatio: chartHeight / chartWidth, + aspectRatio: ASPECT_RATIO, margins, chartWidth, chartHeight, diff --git a/app/charts/shared/containers.tsx b/app/charts/shared/containers.tsx index 8895770ce..438d319e8 100644 --- a/app/charts/shared/containers.tsx +++ b/app/charts/shared/containers.tsx @@ -1,6 +1,5 @@ import { Box, BoxProps } from "@mui/material"; import { makeStyles } from "@mui/styles"; -import clsx from "clsx"; import { select } from "d3-selection"; import { ReactNode, useEffect, useRef } from "react"; @@ -8,72 +7,32 @@ import { useChartState } from "@/charts/shared/chart-state"; import { CalculationToggle } from "@/charts/shared/interactive-filter-calculation-toggle"; import { useObserverRef } from "@/charts/shared/use-size"; import { - ChartConfig, hasChartConfigs, - isConfiguring, + isLayoutingFreeCanvas, useConfiguratorState, } from "@/configurator"; import { useTransitionStore } from "@/stores/transition"; -export const useStyles = makeStyles< - {}, - {}, - ChartConfig["chartType"] | "chartContainer" | "constrainMinHeight" ->(() => ({ +export const useStyles = makeStyles<{}, {}, "chartContainer">(() => ({ chartContainer: { position: "relative", width: "100%", - overflow: "hidden", flexGrow: 1, }, - constrainMinHeight: { - // TODO The aspect ratio is currently set for the whole chart instead of - // affecting only the plot area. Only the plot area should be affected - // otherwise long y-axis ticks squash vertically a chart. - // To remedy, the aspectRatio should be provided via context so that - // the chart state function can get it and apply it to the plot area. - // The ReactGridChartPreview component should also disable this behavior. - aspectRatio: "5 / 2", - minHeight: 300, - }, - - // Chart type specific styles, if we need for example to set a specific aspect-ratio - // for a specific chart type - area: {}, - column: {}, - comboLineColumn: {}, - comboLineDual: {}, - comboLineSingle: {}, - line: {}, - map: {}, - pie: {}, - scatterplot: {}, - table: {}, })); -export const ChartContainer = ({ - children, - type, -}: { - children: ReactNode; - type: ChartConfig["chartType"]; -}) => { +export const ChartContainer = ({ children }: { children: ReactNode }) => { const [state] = useConfiguratorState(hasChartConfigs); + const isFreeCanvas = isLayoutingFreeCanvas(state); const ref = useObserverRef(); + const { bounds } = useChartState(); const classes = useStyles(); return ( diff --git a/app/components/react-grid.tsx b/app/components/react-grid.tsx index 321fe5588..8b901954e 100644 --- a/app/components/react-grid.tsx +++ b/app/components/react-grid.tsx @@ -8,7 +8,10 @@ import { ComponentProps, useEffect, useMemo, useRef, useState } from "react"; import { Layout, Responsive, WidthProvider } from "react-grid-layout"; import { match } from "ts-pattern"; -import { useStyles as useChartContainerStyles } from "@/charts/shared/containers"; +import { + CHART_CLASS_NAME, + useStyles as useChartContainerStyles, +} from "@/charts/shared/containers"; import { getChartWrapperId } from "@/components/chart-panel"; import { useTimeout } from "@/hooks/use-timeout"; @@ -57,7 +60,6 @@ const useStyles = makeStyles((theme: Theme) => ({ // Customization boxSizing: "border-box", - overflow: "hidden", }, "& .react-grid-item img": { pointerEvents: "none", @@ -196,7 +198,7 @@ const useStyles = makeStyles((theme: Theme) => ({ }, })); -const CHART_GRID_MIN_HEIGHT = 100; +export const CHART_GRID_MIN_HEIGHT = 100; export const ChartGridLayout = ({ children, @@ -241,13 +243,13 @@ export const ChartGridLayout = ({ if (wrapper) { const chartContainer: HTMLDivElement | null = wrapper.querySelector( - `.${chartContainerClasses.chartContainer}` + `.${chartContainerClasses.chartContainer} > .${CHART_CLASS_NAME}` ); if (chartContainer) { const minWrapperHeight = wrapper.scrollHeight - - chartContainer.clientHeight + + chartContainer.scrollHeight + CHART_GRID_MIN_HEIGHT; minH = Math.max(MIN_H, Math.ceil(minWrapperHeight / ROW_HEIGHT)); } diff --git a/app/configurator/configurator-state/index.tsx b/app/configurator/configurator-state/index.tsx index a723475c0..187eb35f0 100644 --- a/app/configurator/configurator-state/index.tsx +++ b/app/configurator/configurator-state/index.tsx @@ -231,6 +231,16 @@ export const isLayouting = ( return s.state === "LAYOUTING"; }; +export const isLayoutingFreeCanvas = ( + s: ConfiguratorStateWithChartConfigs +): s is ConfiguratorStateLayouting => { + return ( + !isConfiguring(s) && + s.layout.type === "dashboard" && + s.layout.layout === "canvas" + ); +}; + export const isPublishing = ( s: ConfiguratorState ): s is ConfiguratorStatePublishing => { diff --git a/app/docs/charts.stories.tsx b/app/docs/charts.stories.tsx index e77d14103..87119057f 100644 --- a/app/docs/charts.stories.tsx +++ b/app/docs/charts.stories.tsx @@ -90,7 +90,7 @@ const ColumnsStory = { dimensionsByIri={keyBy(columnDimensions, (d: Dimension) => d.iri)} chartConfig={chartConfig} > - + @@ -149,7 +149,7 @@ const ScatterplotStory = { measuresByIri={keyBy(scatterplotMeasures, (d) => d.iri)} chartConfig={scatterplotChartConfig} > - + diff --git a/app/docs/datatable.stories.tsx b/app/docs/datatable.stories.tsx index 5ba07cbd2..8a4220412 100644 --- a/app/docs/datatable.stories.tsx +++ b/app/docs/datatable.stories.tsx @@ -33,7 +33,7 @@ const DataTableStory: StoryObj = { measuresByIri={keyBy(tableMeasures, (d) => d.iri)} chartConfig={tableConfig} > - + diff --git a/app/docs/lines.stories.tsx b/app/docs/lines.stories.tsx index ba872ebd3..3c5380ccb 100644 --- a/app/docs/lines.stories.tsx +++ b/app/docs/lines.stories.tsx @@ -68,7 +68,7 @@ const LineChartStory = () => ( measuresByIri={keyBy(measures, (d) => d.iri)} chartConfig={chartConfig} > - +