diff --git a/.loki/reference/chrome_laptop_static_viz_PieChart_Truncated_Total.png b/.loki/reference/chrome_laptop_static_viz_PieChart_Truncated_Total.png new file mode 100644 index 0000000000000000000000000000000000000000..c44c4bbaabd94cae4bdb4f09bffab74aaf67c6a9 Binary files /dev/null and b/.loki/reference/chrome_laptop_static_viz_PieChart_Truncated_Total.png differ diff --git a/e2e/test/scenarios/visualizations-charts/pie_chart.cy.spec.js b/e2e/test/scenarios/visualizations-charts/pie_chart.cy.spec.js index c2b2c9db09fa00a48c9e3d3d487ad4c23add296a..2cc2e48c08fdbdb30c216629d20289b46d70a86d 100644 --- a/e2e/test/scenarios/visualizations-charts/pie_chart.cy.spec.js +++ b/e2e/test/scenarios/visualizations-charts/pie_chart.cy.spec.js @@ -85,6 +85,39 @@ describe("scenarios > visualizations > pie chart", () => { cy.findByText("TOTAL").should("be.visible"); }); }); + + it("should truncate the center dimension label if it overflows", () => { + visitQuestionAdhoc({ + dataset_query: { + type: "query", + query: { + "source-table": PRODUCTS_ID, + expressions: { + category_foo: [ + "concat", + ["field", PRODUCTS.CATEGORY, null], + " the quick brown fox jumps over the lazy dog", + ], + }, + aggregation: [["count"]], + breakout: [["expression", "category_foo"]], + }, + database: SAMPLE_DB_ID, + }, + display: "pie", + }); + + // Ensure chart renders before hovering the legend item + cy.findByTestId("query-visualization-root").within(() => { + cy.findByText("TOTAL"); + }); + + cy.findAllByTestId("legend-item").eq(0).realHover(); + + cy.findByTestId("query-visualization-root").within(() => { + cy.findByText("DOOHICKEY THE QUICK BROWN FOX J…"); + }); + }); }); function ensurePieChartRendered(rows, totalValue) { diff --git a/frontend/src/metabase/static-viz/components/PieChart/PieChart.stories.tsx b/frontend/src/metabase/static-viz/components/PieChart/PieChart.stories.tsx index 77f4dfef22993b81a707513a5f0fe787af3b73b3..efaf15f55db55db68cfc378dc821782eb0c7fd16 100644 --- a/frontend/src/metabase/static-viz/components/PieChart/PieChart.stories.tsx +++ b/frontend/src/metabase/static-viz/components/PieChart/PieChart.stories.tsx @@ -203,6 +203,13 @@ NumDecimalPlacesLegend.args = { renderingContext, }; +export const TruncatedTotal = Template.bind({}); +TruncatedTotal.args = { + rawSeries: data.truncatedTotal as any, + dashcardSettings: {}, + renderingContext, +}; + export const UnaggregatedDimension = Template.bind({}); UnaggregatedDimension.args = { rawSeries: data.unaggregatedDimension as any, diff --git a/frontend/src/metabase/static-viz/components/PieChart/stories-data/index.ts b/frontend/src/metabase/static-viz/components/PieChart/stories-data/index.ts index 8fcfb43405d824356ce99fc26cbc4a94b273177b..94cbd832dca8dccb322e9d4381d7a938fe50897a 100644 --- a/frontend/src/metabase/static-viz/components/PieChart/stories-data/index.ts +++ b/frontend/src/metabase/static-viz/components/PieChart/stories-data/index.ts @@ -35,6 +35,7 @@ import singleDimension from "./single-dimension.json"; import smallMinimumSlicePercentage from "./small-min-slice-percentage.json"; import sortedMetricCol from "./sorted-metric-col.json"; import tinySlicesDisappear43766 from "./tiny-slices-disappear-43766.json"; +import truncatedTotal from "./truncated-total.json"; import unaggregatedDimension from "./unaggregated-dimension.json"; import zeroMinimumSlicePercentage from "./zero-min-slice-percentage.json"; @@ -63,6 +64,7 @@ export const data = { nullDimension, numDecimalPlacesChart, numDecimalPlacesLegend, + truncatedTotal, unaggregatedDimension, singleDimension, longDimensionName, diff --git a/frontend/src/metabase/static-viz/components/PieChart/stories-data/truncated-total.json b/frontend/src/metabase/static-viz/components/PieChart/stories-data/truncated-total.json new file mode 100644 index 0000000000000000000000000000000000000000..cd260f3d5786284a859deba686a3fe095d8dd612 --- /dev/null +++ b/frontend/src/metabase/static-viz/components/PieChart/stories-data/truncated-total.json @@ -0,0 +1,297 @@ +[ + { + "card": { + "original_card_id": 346, + "public_uuid": null, + "parameter_usage_count": 0, + "created_at": "2024-06-05T23:14:45.449636Z", + "parameters": [], + "metabase_version": "v0.2.0-SNAPSHOT (a727fad)", + "collection": { + "authority_level": null, + "description": null, + "archived": false, + "trashed_from_location": null, + "slug": "pie", + "name": "Pie", + "personal_owner_id": null, + "type": null, + "is_sample": false, + "id": 23, + "entity_id": "hy98llXqY3i_uwtm5ISY_", + "location": "/5/", + "namespace": null, + "is_personal": false, + "created_at": "2024-06-03T19:59:20.91934Z" + }, + "visualization_settings": { + "column_settings": { + "[\"name\",\"sum\"]": { + "number_style": "decimal", + "scale": 0.001, + "suffix": " kg", + "prefix": "incredibly super duper long prefix for the weight of this penguin that weighs ", + "decimals": 2 + } + } + }, + "last-edit-info": { + "id": 1, + "email": "emmad@metabase.com", + "first_name": "Emmad", + "last_name": "Usmani", + "timestamp": "2024-06-05T23:14:45.710965Z" + }, + "collection_preview": true, + "entity_id": "-lZmLkBMVbB0U5puWZnOu", + "display": "pie", + "parameter_mappings": [], + "id": 346, + "dataset_query": { + "database": 2, + "type": "query", + "query": { + "aggregation": [ + [ + "sum", + [ + "field", + "body_mass_g", + { + "base-type": "type/Float" + } + ] + ] + ], + "breakout": [ + [ + "field", + "species", + { + "base-type": "type/Text" + } + ] + ], + "source-table": "card__92" + } + }, + "cache_ttl": null, + "embedding_params": null, + "made_public_by_id": null, + "updated_at": "2024-06-06T00:13:07.782678Z", + "moderation_reviews": [], + "creator_id": 1, + "average_query_time": 319.5882352941176, + "type": "question", + "last_used_at": "2024-06-06T00:13:07.782678Z", + "dashboard_count": 1, + "last_query_start": "2024-06-06T00:13:07.345851Z", + "name": "Pie - Column Settings - Penguins sum of body mass by species", + "query_type": "query", + "collection_id": 23, + "enable_embedding": false, + "database_id": 2, + "trashed_from_collection_id": null, + "can_write": true, + "initially_published_at": null, + "creator": { + "email": "emmad@metabase.com", + "first_name": "Emmad", + "last_login": "2024-06-05T21:21:49.288174Z", + "is_qbnewb": false, + "is_superuser": true, + "id": 1, + "last_name": "Usmani", + "date_joined": "2023-11-21T21:25:41.062104Z", + "common_name": "Emmad Usmani" + }, + "result_metadata": [ + { + "semantic_type": "type/Category", + "name": "species", + "field_ref": [ + "field", + "species", + { + "base-type": "type/Text" + } + ], + "effective_type": "type/Text", + "id": 1403, + "visibility_type": "normal", + "display_name": "Species", + "fingerprint": { + "global": { + "distinct-count": 3, + "nil%": 0 + }, + "type": { + "type/Text": { + "percent-json": 0, + "percent-url": 0, + "percent-email": 0, + "percent-state": 0, + "average-length": 6.593023255813954 + } + } + }, + "base_type": "type/Text" + }, + { + "display_name": "Sum of Body Mass G", + "semantic_type": "type/Quantity", + "field_ref": ["aggregation", 0], + "name": "sum", + "base_type": "type/Float", + "effective_type": "type/Float", + "fingerprint": { + "global": { + "distinct-count": 3, + "nil%": 0 + }, + "type": { + "type/Number": { + "min": 253850, + "q1": 330087.5, + "q3": 607962.5, + "max": 624350, + "sd": 197720.9966088579, + "avg": 479000 + } + } + } + } + ], + "can_run_adhoc_query": true, + "table_id": 151, + "collection_position": null, + "view_count": 12, + "archived": false, + "description": null, + "cache_invalidated_at": null, + "displayIsLocked": true + }, + "data": { + "rows": [ + ["Adelie", 558800], + ["Chinstrap", 253850], + ["Gentoo", 624350] + ], + "cols": [ + { + "database_type": "varchar", + "semantic_type": "type/Category", + "table_id": 151, + "name": "species", + "source": "breakout", + "field_ref": [ + "field", + "species", + { + "base-type": "type/Text" + } + ], + "effective_type": "type/Text", + "id": 1403, + "position": 1, + "visibility_type": "normal", + "display_name": "Species", + "fingerprint": { + "global": { + "distinct-count": 3, + "nil%": 0 + }, + "type": { + "type/Text": { + "percent-json": 0, + "percent-url": 0, + "percent-email": 0, + "percent-state": 0, + "average-length": 6.593023255813954 + } + } + }, + "base_type": "type/Text" + }, + { + "semantic_type": "type/Quantity", + "base_type": "type/Float", + "name": "sum", + "display_name": "Sum of Body Mass G", + "source": "aggregation", + "field_ref": ["aggregation", 0], + "aggregation_index": 0, + "effective_type": "type/Float" + } + ], + "native_form": { + "query": "SELECT \"source\".\"species\" AS \"species\", SUM(\"source\".\"body_mass_g\") AS \"sum\" FROM (SELECT \"csv_upload_data\".\"csv_upload_penguins_20231130074915\".\"id\" AS \"id\", \"csv_upload_data\".\"csv_upload_penguins_20231130074915\".\"species\" AS \"species\", \"csv_upload_data\".\"csv_upload_penguins_20231130074915\".\"island\" AS \"island\", \"csv_upload_data\".\"csv_upload_penguins_20231130074915\".\"bill_length_mm\" AS \"bill_length_mm\", \"csv_upload_data\".\"csv_upload_penguins_20231130074915\".\"bill_depth_mm\" AS \"bill_depth_mm\", \"csv_upload_data\".\"csv_upload_penguins_20231130074915\".\"flipper_length_mm\" AS \"flipper_length_mm\", \"csv_upload_data\".\"csv_upload_penguins_20231130074915\".\"body_mass_g\" AS \"body_mass_g\", \"csv_upload_data\".\"csv_upload_penguins_20231130074915\".\"sex\" AS \"sex\" FROM \"csv_upload_data\".\"csv_upload_penguins_20231130074915\") AS \"source\" GROUP BY \"source\".\"species\" ORDER BY \"source\".\"species\" ASC", + "params": null + }, + "dataset": true, + "model": true, + "format-rows?": true, + "results_timezone": "America/Los_Angeles", + "results_metadata": { + "columns": [ + { + "semantic_type": "type/Category", + "name": "species", + "field_ref": [ + "field", + "species", + { + "base-type": "type/Text" + } + ], + "effective_type": "type/Text", + "id": 1403, + "visibility_type": "normal", + "display_name": "Species", + "fingerprint": { + "global": { + "distinct-count": 3, + "nil%": 0 + }, + "type": { + "type/Text": { + "percent-json": 0, + "percent-url": 0, + "percent-email": 0, + "percent-state": 0, + "average-length": 6.593023255813954 + } + } + }, + "base_type": "type/Text" + }, + { + "display_name": "Sum of Body Mass G", + "semantic_type": "type/Quantity", + "field_ref": ["aggregation", 0], + "name": "sum", + "base_type": "type/Float", + "effective_type": "type/Float", + "fingerprint": { + "global": { + "distinct-count": 3, + "nil%": 0 + }, + "type": { + "type/Number": { + "min": 253850, + "q1": 330087.5, + "q3": 607962.5, + "max": 624350, + "sd": 197720.9966088579, + "avg": 479000 + } + } + } + } + ] + }, + "insights": null + } + } +] diff --git a/frontend/src/metabase/visualizations/echarts/pie/constants.ts b/frontend/src/metabase/visualizations/echarts/pie/constants.ts index c6fc5d44377bf1186c3a1dd3309bb0f3a4761d3c..3c5fb32bbcec3f2e1d12782bb836c36727276451 100644 --- a/frontend/src/metabase/visualizations/echarts/pie/constants.ts +++ b/frontend/src/metabase/visualizations/echarts/pie/constants.ts @@ -2,7 +2,6 @@ import { t } from "ttag"; export const DIMENSIONS = { maxSideLength: 550, - totalDiameterThreshold: 120, padding: { legend: 16, side: 12, @@ -17,6 +16,11 @@ export const DIMENSIONS = { padding: 4, }, }, + total: { + minWidth: 120, + valueFontSize: 22, + labelFontSize: 14, + }, }; export const SLICE_THRESHOLD = 0.025; // approx 1 degree in percentage diff --git a/frontend/src/metabase/visualizations/echarts/pie/option.ts b/frontend/src/metabase/visualizations/echarts/pie/option.ts index 9db471d17d0561fb880f9aada68864dcfdc22483..4274a38486f3ae2cdc5318b950bfe24437dc471e 100644 --- a/frontend/src/metabase/visualizations/echarts/pie/option.ts +++ b/frontend/src/metabase/visualizations/echarts/pie/option.ts @@ -2,6 +2,7 @@ import Color from "color"; import type { EChartsOption } from "echarts"; import { getTextColorForBackground } from "metabase/lib/colors"; +import { truncateText } from "metabase/static-viz/lib/text"; import type { ComputedVisualizationSettings, RenderingContext, @@ -36,21 +37,27 @@ function getTotalGraphicOption( let labelText = ""; // Don't display any text if there isn't enough width - const hasSufficientWidth = - outerRadius * 2 >= DIMENSIONS.totalDiameterThreshold; + const hasSufficientWidth = outerRadius * 2 >= DIMENSIONS.total.minWidth; if (hasSufficientWidth && settings["pie.show_total"]) { - valueText = formatters.formatMetric( - hoveredIndex != null - ? chartModel.slices[hoveredIndex].data.displayValue - : chartModel.total, + valueText = truncateText( + formatters.formatMetric( + hoveredIndex != null + ? chartModel.slices[hoveredIndex].data.displayValue + : chartModel.total, + ), + outerRadius, // innerRadius technically makes more sense, but looks too narrow in practice + DIMENSIONS.total.valueFontSize, ); - labelText = + labelText = truncateText( hoveredIndex != null ? formatters .formatDimension(chartModel.slices[hoveredIndex].data.key) .toUpperCase() - : TOTAL_TEXT; + : TOTAL_TEXT, + outerRadius, + DIMENSIONS.total.labelFontSize, + ); } return { @@ -59,10 +66,11 @@ function getTotalGraphicOption( left: "center", children: [ { + // Value type: "text", cursor: "text", style: { - fontSize: "22px", + fontSize: `${DIMENSIONS.total.valueFontSize}px`, fontWeight: "700", textAlign: "center", fontFamily: renderingContext.fontFamily, @@ -71,11 +79,12 @@ function getTotalGraphicOption( }, }, { + // Label type: "text", cursor: "text", top: 26, style: { - fontSize: "14px", + fontSize: `${DIMENSIONS.total.labelFontSize}px`, fontWeight: "700", textAlign: "center", fontFamily: renderingContext.fontFamily,