1
0
Эх сурвалжийг харах

Metric control geometry (issues #2141 and #2142) (#2346)

Fabien Lelaquais 5 сар өмнө
parent
commit
f7deb54f8d

+ 154 - 44
frontend/taipy-gui/src/components/Taipy/Metric.spec.tsx

@@ -163,15 +163,15 @@ const lightTemplate = {
 
 
 describe("Metric Component", () => {
 describe("Metric Component", () => {
     it("renders", async () => {
     it("renders", async () => {
-        const { getByTestId } = render(<Metric testId="test-id" />);
-        const elt = getByTestId("test-id");
-        expect(elt.tagName).toBe("DIV");
+        const { container } = render(<Metric className="test" />);
+        const elt = container.querySelector(".test");
+        expect(elt?.tagName).toBe("DIV");
     });
     });
 
 
     it("displays the right info for class", async () => {
     it("displays the right info for class", async () => {
-        const { getByTestId } = render(<Metric testId="test-id" className={"taipy-gauge"} />);
-        const elt = getByTestId("test-id");
-        expect(elt).toHaveClass("taipy-gauge");
+        const { container } = render(<Metric className="test" />);
+        const elt = container.querySelector(".test");
+        expect(elt).toHaveClass("test");
     });
     });
 
 
     it("sets the title when provided", async () => {
     it("sets the title when provided", async () => {
@@ -203,9 +203,9 @@ describe("Metric Component", () => {
     });
     });
 
 
     it("sets the template when provided", async () => {
     it("sets the template when provided", async () => {
-        render(<Metric template={JSON.stringify(template)} />);
+        const { container } = render(<Metric template={JSON.stringify(template)} />);
         await waitFor(() => {
         await waitFor(() => {
-            const elt = document.querySelector(".main-svg");
+            const elt = container.querySelector(".main-svg");
             expect(elt).toHaveStyle({
             expect(elt).toHaveStyle({
                 backgroundColor: "rgb(255, 0, 0)",
                 backgroundColor: "rgb(255, 0, 0)",
             });
             });
@@ -213,23 +213,23 @@ describe("Metric Component", () => {
     });
     });
 
 
     it("processes colorMap prop correctly", async () => {
     it("processes colorMap prop correctly", async () => {
-        render(<Metric colorMap={JSON.stringify(colorMap)} />);
+        const { container } = render(<Metric colorMap={JSON.stringify(colorMap)} />);
         await waitFor(() => {
         await waitFor(() => {
-            const elts = document.querySelectorAll(".bg-arc");
-            const redElt = Array.from(elts[1].children);
+            const elements = container.querySelectorAll(".bg-arc");
+            const redElt = Array.from(elements[1].children);
             const redEltHasRedFill = redElt.some((elt) => (elt as HTMLElement).style.fill === "rgb(255, 0, 0)");
             const redEltHasRedFill = redElt.some((elt) => (elt as HTMLElement).style.fill === "rgb(255, 0, 0)");
             expect(redEltHasRedFill).toBeTruthy();
             expect(redEltHasRedFill).toBeTruthy();
 
 
-            const blueElt = Array.from(elts[2].children);
+            const blueElt = Array.from(elements[2].children);
             const blueEltHasBlueFill = blueElt.some((elt) => (elt as HTMLElement).style.fill === "rgb(0, 0, 255)");
             const blueEltHasBlueFill = blueElt.some((elt) => (elt as HTMLElement).style.fill === "rgb(0, 0, 255)");
             expect(blueEltHasBlueFill).toBeTruthy();
             expect(blueEltHasBlueFill).toBeTruthy();
         });
         });
     });
     });
 
 
     it("processes delta prop correctly when delta is defined", async () => {
     it("processes delta prop correctly when delta is defined", async () => {
-        render(<Metric delta={10} testId="test-id" />);
+        const { container } = render(<Metric delta={10} />);
         await waitFor(() => {
         await waitFor(() => {
-            const elt = document.querySelector(".delta");
+            const elt = container.querySelector(".delta");
             if (elt) {
             if (elt) {
                 expect(elt.textContent).toContain("10");
                 expect(elt.textContent).toContain("10");
             } else {
             } else {
@@ -239,58 +239,168 @@ describe("Metric Component", () => {
     });
     });
 
 
     it("applies style correctly when deltaColor is set", async () => {
     it("applies style correctly when deltaColor is set", async () => {
-        render(<Metric delta={10} deltaColor="#FF4136" testId="test-id" />);
+        const { container } = render(<Metric delta={10} deltaColor="#FF4136" />);
         await waitFor(() => {
         await waitFor(() => {
-            const elt = document.querySelector(".delta");
+            const elt = container.querySelector(".delta");
             expect(elt).toHaveStyle({
             expect(elt).toHaveStyle({
-                    fill: "rgb(255, 65, 54)"
-                });
+                fill: "rgb(255, 65, 54)",
+            });
         });
         });
     });
     });
 
 
     it("applies style correctly when deltaColor is set invert", async () => {
     it("applies style correctly when deltaColor is set invert", async () => {
-        render(<Metric delta={10} deltaColor="invert" testId="test-id" />);
+        const { container } = render(<Metric delta={10} deltaColor="invert" />);
         await waitFor(() => {
         await waitFor(() => {
-            const elt = document.querySelector(".delta");
+            const elt = container.querySelector(".delta");
             expect(elt).toHaveStyle({
             expect(elt).toHaveStyle({
-                    fill: "rgb(255, 65, 54)"
-                });
+                fill: "rgb(255, 65, 54)",
+            });
         });
         });
     });
     });
 
 
     it("processes type and threshold props correctly when type is linear", async () => {
     it("processes type and threshold props correctly when type is linear", async () => {
-        render(<Metric type="linear" threshold={50} testId="test-id" />);
+        const { container } = render(<Metric type="linear" threshold={50} />);
         await waitFor(() => {
         await waitFor(() => {
-            const elt = document.querySelector(".bullet");
+            const elt = container.querySelector(".bullet");
             expect(elt).toBeInTheDocument();
             expect(elt).toBeInTheDocument();
         });
         });
     });
     });
 
 
     it("processes type and threshold props correctly when type is not linear", async () => {
     it("processes type and threshold props correctly when type is not linear", async () => {
-        render(<Metric type="angular" threshold={50} testId="test-id" />);
+        const { container } = render(<Metric type="angular" threshold={50} />);
         await waitFor(() => {
         await waitFor(() => {
-            const elt = document.querySelector(".angular");
+            const elt = container.querySelector(".angular");
             expect(elt).toBeInTheDocument();
             expect(elt).toBeInTheDocument();
         });
         });
     });
     });
 
 
-    it("applies style correctly when height is undefined", async () => {
-        render(<Metric testId="test-id" />);
+    it("applies style correctly when height and width are undefined", async () => {
+        const { container } = render(<Metric />);
+        await waitFor(() => {
+            const elt = container.querySelector(".js-plotly-plot");
+            expect(elt).toHaveStyle({
+                width: "20vw",
+                height: "20vh",
+            });
+        });
+    });
+
+    it("applies style correctly when height is set to 100px", async () => {
+        const { container } = render(<Metric height="100px" />);
+        await waitFor(() => {
+            const elt = container.querySelector(".js-plotly-plot");
+            expect(elt).toHaveStyle({
+                height: "100px",
+            });
+        });
+    });
+
+    it("applies style correctly when height is set to 30em", async () => {
+        const { container } = render(<Metric height="30em" />);
         await waitFor(() => {
         await waitFor(() => {
-            const elt = document.querySelector(".js-plotly-plot");
+            const elt = container.querySelector(".js-plotly-plot");
             expect(elt).toHaveStyle({
             expect(elt).toHaveStyle({
-                width: "100%",
-                position: "relative",
-                display: "inline-block",
+                height: "30em",
             });
             });
         });
         });
     });
     });
 
 
-    it("processes type prop correctly when type is none", async () => {
-        render(<Metric type="none" testId="test-id" />);
+    it("applies style correctly when height is set to 30%", async () => {
+        const { container } = render(<Metric height="30%" />);
+        await waitFor(() => {
+            const elt = container.querySelector(".js-plotly-plot");
+            expect(elt).toHaveStyle({
+                height: "30%",
+            });
+        });
+    });
+
+    it("applies style correctly when height is set to 30vh", async () => {
+        const { container } = render(<Metric height="30vh" />);
+        await waitFor(() => {
+            const elt = container.querySelector(".js-plotly-plot");
+            expect(elt).toHaveStyle({
+                height: "30vh",
+            });
+        });
+    });
+
+    it("applies style correctly when height is set to 30vw", async () => {
+        const { container } = render(<Metric height="30vw" />);
+        await waitFor(() => {
+            const elt = container.querySelector(".js-plotly-plot");
+            expect(elt).toHaveStyle({
+                height: "30vw",
+            });
+        });
+    });
+
+    it("applies style correctly when width is set to 100px", async () => {
+        const { container } = render(<Metric width="100px" />);
+        await waitFor(() => {
+            const elt = container.querySelector(".js-plotly-plot");
+            expect(elt).toHaveStyle({
+                width: "100px",
+            });
+        });
+    });
+
+    it("applies style correctly when width is set to 30em", async () => {
+        const { container } = render(<Metric width="30em" />);
+        await waitFor(() => {
+            const elt = container.querySelector(".js-plotly-plot");
+            expect(elt).toHaveStyle({
+                width: "30em",
+            });
+        });
+    });
+
+    it("applies style correctly when width is set to 30%", async () => {
+        const { container } = render(<Metric width="30%" />);
+        await waitFor(() => {
+            const elt = container.querySelector(".js-plotly-plot");
+            expect(elt).toHaveStyle({
+                width: "30%",
+            });
+        });
+    });
+
+    it("applies style correctly when width is set to 30vh", async () => {
+        const { container } = render(<Metric width="30vh" />);
+        await waitFor(() => {
+            const elt = container.querySelector(".js-plotly-plot");
+            expect(elt).toHaveStyle({
+                width: "30vh",
+            });
+        });
+    });
+
+    it("applies style correctly when width is set to 30vw", async () => {
+        const { container } = render(<Metric width="30vw" />);
+        await waitFor(() => {
+            const elt = container.querySelector(".js-plotly-plot");
+            expect(elt).toHaveStyle({
+                width: "30vw",
+            });
+        });
+    });
+
+
+    it("processes type prop correctly when type is none (string)", async () => {
+        const { container } = render(<Metric type="none" />);
+        await waitFor(() => {
+            const angularElm = container.querySelector(".angular");
+            const angularAxis = container.querySelector(".angularaxis");
+            expect(angularElm).not.toBeInTheDocument();
+            expect(angularAxis).not.toBeInTheDocument();
+        });
+    });
+
+    it("processes type prop correctly when type is None", async () => {
+        const { container } = render(<Metric type="None" />);
         await waitFor(() => {
         await waitFor(() => {
-            const angularElm = document.querySelector(".angular");
-            const angularAxis = document.querySelector(".angularaxis");
+            const angularElm = container.querySelector(".angular");
+            const angularAxis = container.querySelector(".angularaxis");
             expect(angularElm).not.toBeInTheDocument();
             expect(angularElm).not.toBeInTheDocument();
             expect(angularAxis).not.toBeInTheDocument();
             expect(angularAxis).not.toBeInTheDocument();
         });
         });
@@ -303,13 +413,13 @@ describe("Metric Component", () => {
             },
             },
         });
         });
 
 
-        render(
+        const { container } = render(
             <ThemeProvider theme={darkTheme}>
             <ThemeProvider theme={darkTheme}>
-                <Metric template_Dark_={JSON.stringify(darkTemplate)} testId="test-id" />
+                <Metric template_Dark_={JSON.stringify(darkTemplate)} />
             </ThemeProvider>,
             </ThemeProvider>,
         );
         );
         await waitFor(() => {
         await waitFor(() => {
-            const elt = document.querySelector(".main-svg");
+            const elt = container.querySelector(".main-svg");
             expect(elt).toHaveStyle({
             expect(elt).toHaveStyle({
                 backgroundColor: "rgb(31, 47, 68)",
                 backgroundColor: "rgb(31, 47, 68)",
             });
             });
@@ -323,13 +433,13 @@ describe("Metric Component", () => {
             },
             },
         });
         });
 
 
-        render(
+        const { container } = render(
             <ThemeProvider theme={lightTheme}>
             <ThemeProvider theme={lightTheme}>
-                <Metric template_Light_={JSON.stringify(lightTemplate)} testId="test-id" />
+                <Metric template_Light_={JSON.stringify(lightTemplate)} />
             </ThemeProvider>,
             </ThemeProvider>,
         );
         );
         await waitFor(() => {
         await waitFor(() => {
-            const elt = document.querySelector(".main-svg");
+            const elt = container.querySelector(".main-svg");
             expect(elt).toHaveStyle({
             expect(elt).toHaveStyle({
                 backgroundColor: "rgb(255, 255, 255)",
                 backgroundColor: "rgb(255, 255, 255)",
             });
             });
@@ -338,14 +448,14 @@ describe("Metric Component", () => {
 
 
     it.skip("logs an error when template_Dark_ prop is not a valid JSON string", () => {
     it.skip("logs an error when template_Dark_ prop is not a valid JSON string", () => {
         const consoleSpy = jest.spyOn(console, "info");
         const consoleSpy = jest.spyOn(console, "info");
-        render(<Metric template_Dark_="not a valid JSON string" testId="test-id" />);
+        render(<Metric template_Dark_="not a valid JSON string" />);
         expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining("Error while parsing Metric.template"));
         expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining("Error while parsing Metric.template"));
         consoleSpy.mockRestore();
         consoleSpy.mockRestore();
     }); // TODO: Not working at the moment, need to fix
     }); // TODO: Not working at the moment, need to fix
 
 
     it("logs an error when template_Light_ prop is not a valid JSON string", () => {
     it("logs an error when template_Light_ prop is not a valid JSON string", () => {
         const consoleSpy = jest.spyOn(console, "info");
         const consoleSpy = jest.spyOn(console, "info");
-        render(<Metric template_Light_="not a valid JSON string" testId="test-id" />);
+        render(<Metric template_Light_="not a valid JSON string" />);
         expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining("Error while parsing Metric.template"));
         expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining("Error while parsing Metric.template"));
         consoleSpy.mockRestore();
         consoleSpy.mockRestore();
     });
     });

+ 34 - 17
frontend/taipy-gui/src/components/Taipy/Metric.tsx

@@ -42,7 +42,6 @@ interface MetricProps extends TaipyBaseProps, TaipyHoverProps {
     showValue?: boolean;
     showValue?: boolean;
     colorMap?: string;
     colorMap?: string;
     title?: string;
     title?: string;
-    testId?: string;
     layout?: string;
     layout?: string;
     defaultLayout?: string;
     defaultLayout?: string;
     width?: string | number;
     width?: string | number;
@@ -52,18 +51,33 @@ interface MetricProps extends TaipyBaseProps, TaipyHoverProps {
     template_Light_?: string;
     template_Light_?: string;
 }
 }
 
 
-const emptyLayout = {} as Partial<Layout>;
-const defaultStyle = { position: "relative", display: "inline-block", width: "100%" } as CSSProperties;
+const defaultLayout = { margin: { l: 50, r: 50, t: 50, b: 50 } } as Partial<Layout>;
+const defaultStyle = {
+    position: "relative",
+    display: "inline-block",
+    /*
+    * When updating the width and height, be sure to adjust the corresponding Metric values in the viselements.json file accordingly.
+    * */
+    width: "20vw",
+    height: "20vh",
+} as CSSProperties;
 const skeletonStyle = { ...defaultStyle, minHeight: "7em" };
 const skeletonStyle = { ...defaultStyle, minHeight: "7em" };
 const plotConfig = { displaylogo: false };
 const plotConfig = { displaylogo: false };
 
 
+const normalizeSize = (val: string | number | undefined): string | undefined => {
+    if (typeof val === "number" || (typeof val === "string" && /^\d+$/.test(val))) {
+        return `${val}px`;
+    }
+    return val;
+};
+
 const Metric = (props: MetricProps) => {
 const Metric = (props: MetricProps) => {
     const { showValue = true } = props;
     const { showValue = true } = props;
     const value = useDynamicProperty(props.value, props.defaultValue, 0);
     const value = useDynamicProperty(props.value, props.defaultValue, 0);
     const threshold = useDynamicProperty(props.threshold, props.defaultThreshold, undefined);
     const threshold = useDynamicProperty(props.threshold, props.defaultThreshold, undefined);
     const delta = useDynamicProperty(props.delta, props.defaultDelta, undefined);
     const delta = useDynamicProperty(props.delta, props.defaultDelta, undefined);
     const className = useClassNames(props.libClassName, props.dynamicClassName, props.className);
     const className = useClassNames(props.libClassName, props.dynamicClassName, props.className);
-    const baseLayout = useDynamicJsonProperty(props.layout, props.defaultLayout || "", emptyLayout);
+    const baseLayout = useDynamicJsonProperty(props.layout, props.defaultLayout || "", defaultLayout);
     const hover = useDynamicProperty(props.hoverText, props.defaultHoverText, undefined);
     const hover = useDynamicProperty(props.hoverText, props.defaultHoverText, undefined);
     const theme = useTheme();
     const theme = useTheme();
 
 
@@ -87,22 +101,22 @@ const Metric = (props: MetricProps) => {
     }, [props.colorMap, props.max]);
     }, [props.colorMap, props.max]);
 
 
     const data = useMemo(() => {
     const data = useMemo(() => {
-        const mode = props.type === "none" ? [] : ["gauge"];
+        const mode = typeof props.type === "string" && props.type.toLowerCase() === "none" ? [] : ["gauge"];
         showValue && mode.push("number");
         showValue && mode.push("number");
         delta !== undefined && mode.push("delta");
         delta !== undefined && mode.push("delta");
         const deltaIncreasing = props.deltaColor
         const deltaIncreasing = props.deltaColor
             ? {
             ? {
-                  color: props.deltaColor == "invert" ? "#FF4136" : props.deltaColor,
-              }
+                color: props.deltaColor == "invert" ? "#FF4136" : props.deltaColor,
+            }
             : undefined;
             : undefined;
         const deltaDecreasing =
         const deltaDecreasing =
             props.deltaColor == "invert"
             props.deltaColor == "invert"
                 ? {
                 ? {
-                      color: "#3D9970",
-                  }
+                    color: "#3D9970",
+                }
                 : props.negativeDeltaColor
                 : props.negativeDeltaColor
-                  ? { color: props.negativeDeltaColor }
-                  : undefined;
+                    ? { color: props.negativeDeltaColor }
+                    : undefined;
         return [
         return [
             {
             {
                 domain: { x: [0, 1], y: [0, 1] },
                 domain: { x: [0, 1], y: [0, 1] },
@@ -158,8 +172,6 @@ const Metric = (props: MetricProps) => {
     const layout = useMemo(() => {
     const layout = useMemo(() => {
         const layout = {
         const layout = {
             ...baseLayout,
             ...baseLayout,
-            height: baseLayout.height !== undefined ? baseLayout.height : props.height,
-            width: baseLayout.width !== undefined ? baseLayout.width : props.width,
         };
         };
         let template = undefined;
         let template = undefined;
         try {
         try {
@@ -185,8 +197,6 @@ const Metric = (props: MetricProps) => {
         return layout as Partial<Layout>;
         return layout as Partial<Layout>;
     }, [
     }, [
         props.title,
         props.title,
-        props.height,
-        props.width,
         props.template,
         props.template,
         props.template_Dark_,
         props.template_Dark_,
         props.template_Light_,
         props.template_Light_,
@@ -194,11 +204,18 @@ const Metric = (props: MetricProps) => {
         baseLayout,
         baseLayout,
     ]);
     ]);
 
 
+    const style = useMemo(() => {
+        const width = props.width ? normalizeSize(props.width) : defaultStyle.width;
+        const height = props.height ? normalizeSize(props.height) : defaultStyle.height;
+
+        return { ...defaultStyle, width, height };
+    }, [props.width, props.height]);
+
     return (
     return (
         <Tooltip title={hover || ""}>
         <Tooltip title={hover || ""}>
-            <Box data-testid={props.testId} className={className}>
+            <Box className={className}>
                 <Suspense fallback={<Skeleton key="skeleton" sx={skeletonStyle} />}>
                 <Suspense fallback={<Skeleton key="skeleton" sx={skeletonStyle} />}>
-                    <Plot data={data} layout={layout} style={defaultStyle} config={plotConfig} useResizeHandler />
+                    <Plot data={data} layout={layout} style={style} config={plotConfig} useResizeHandler />
                 </Suspense>
                 </Suspense>
             </Box>
             </Box>
         </Tooltip>
         </Tooltip>

+ 4 - 4
taipy/gui/viselements.json

@@ -1335,14 +1335,14 @@
                     {
                     {
                         "name": "width",
                         "name": "width",
                         "type": "Union[str,number]",
                         "type": "Union[str,number]",
-                        "default_value": "None",
-                        "doc": "The width of the metric control, in CSS units."
+                        "default_value": "\"20vw\"",
+                        "doc": "The width of the metric control, in CSS units"
                     },
                     },
                     {
                     {
                         "name": "height",
                         "name": "height",
                         "type": "Union[str,number]",
                         "type": "Union[str,number]",
-                        "default_value": "None",
-                        "doc": "The height of the metric control, in CSS units."
+                        "default_value": "\"20vh\"",
+                        "doc": "The height of the metric control, in CSS units"
                     },
                     },
                     {
                     {
                         "name": "layout",
                         "name": "layout",

+ 37 - 0
tests/gui/e2e/test_resizing.py

@@ -0,0 +1,37 @@
+import inspect
+from importlib import util
+
+import pandas
+import pytest
+
+if util.find_spec("playwright"):
+    from playwright._impl._page import Page
+
+from taipy.gui import Gui
+
+
+@pytest.mark.teste2e
+def test_has_default_value(page: Page, gui: Gui, helpers):
+    percentages = [
+        (1852, 50.83),
+        (1856, 45.29),
+        (1860, 39.65),
+        (1864, 55.03),
+    ]
+    data = pandas.DataFrame(percentages, columns=["Year", "%"]) # noqa: F841
+    page_md = "<|{data}|chart|type=bar|x=Year|y=%|>"
+    gui._set_frame(inspect.currentframe())
+    gui.add_page(name="test",page=page_md)
+    helpers.run_e2e(gui)
+    page.goto("./test")
+    page.wait_for_timeout(3000)
+    page.wait_for_selector(".plot-container")
+    page.set_viewport_size({"width": 800, "height": 600})
+    elements = page.locator(
+        'path[style*="vector-effect: non-scaling-stroke; opacity: 1; stroke-width: 0px; fill: rgb(99, 110, 250); fill-opacity: 1;"]') # noqa: E501
+    first_element = elements.first
+    box_before = first_element.bounding_box()
+    page.set_viewport_size({"width": 1920, "height": 1080})
+    page.wait_for_timeout(1000)
+    box_after = first_element.bounding_box()
+    assert box_after["width"] > box_before["width"]