浏览代码

Implement delta_color options (#1538)

* Implemented positiveDeltaColor and negativeDeltaColor

* Add testings for the delta colors

* Remove default values

* Removed symbols

* Implement invert to delta_color

* Updated deleteDecreasing logic and invert testing function

* Update viselelements.json
Satoshi S. 10 月之前
父节点
当前提交
0ae161a824

+ 20 - 0
frontend/taipy-gui/src/components/Taipy/Metric.spec.tsx

@@ -238,6 +238,26 @@ describe("Metric Component", () => {
         });
     });
 
+    it("applies style correctly when deltaColor is set", async () => {
+        render(<Metric delta={10} deltaColor="#FF4136" testId="test-id" />);
+        await waitFor(() => {
+            const elt = document.querySelector(".delta");
+            expect(elt).toHaveStyle({
+                    fill: "rgb(255, 65, 54)"
+                });
+        });
+    });
+
+    it("applies style correctly when deltaColor is set invert", async () => {
+        render(<Metric delta={10} deltaColor="invert" testId="test-id" />);
+        await waitFor(() => {
+            const elt = document.querySelector(".delta");
+            expect(elt).toHaveStyle({
+                    fill: "rgb(255, 65, 54)"
+                });
+        });
+    });
+
     it("processes type and threshold props correctly when type is linear", async () => {
         render(<Metric type="linear" threshold={50} testId="test-id" />);
         await waitFor(() => {

+ 16 - 2
frontend/taipy-gui/src/components/Taipy/Metric.tsx

@@ -33,6 +33,8 @@ interface MetricProps extends TaipyBaseProps, TaipyHoverProps {
     defaultValue?: number
     delta?: number
     defaultDelta?: number
+    deltaColor?: string
+    negativeDeltaColor?: string
     threshold?: number
     defaultThreshold?: number
     testId?: string
@@ -58,7 +60,9 @@ const Metric = (props: MetricProps) => {
     const {
         width = "100%",
         height,
-        showValue = true
+        showValue = true,
+        deltaColor,
+        negativeDeltaColor
     } = props;
     const value = useDynamicProperty(props.value, props.defaultValue, 0)
     const threshold = useDynamicProperty(props.threshold, props.defaultThreshold, undefined)
@@ -88,6 +92,11 @@ const Metric = (props: MetricProps) => {
         const mode = (props.type === "none") ? [] : ["gauge"];
         showValue && mode.push("number");
         (delta !== undefined) && mode.push("delta");
+        const deltaIncreasing = deltaColor ? {
+            color: deltaColor == "invert" ? "#FF4136" : deltaColor } : undefined
+        const deltaDecreasing = deltaColor == "invert" ? {
+                color: "#3D9970"
+            } : negativeDeltaColor ? { color: negativeDeltaColor } : undefined;
         return [
             {
                 domain: {x: [0, 1], y: [0, 1]},
@@ -103,7 +112,10 @@ const Metric = (props: MetricProps) => {
                     reference: typeof value === 'number' && typeof delta === 'number' ? value - delta : undefined,
                     prefix: extractPrefix(props.deltaFormat),
                     suffix: extractSuffix(props.deltaFormat),
-                    valueformat: sprintfToD3Converter(props.deltaFormat)
+                    valueformat: sprintfToD3Converter(props.deltaFormat),
+                    increasing: deltaIncreasing,
+                    decreasing: deltaDecreasing
+
                 } as Partial<Delta>,
                 gauge: {
                     axis: {
@@ -130,6 +142,8 @@ const Metric = (props: MetricProps) => {
         props.type,
         value,
         showValue,
+        deltaColor,
+        negativeDeltaColor,
         delta,
         threshold,
         colorMap

+ 3 - 1
taipy/gui/_renderers/factory.py

@@ -353,6 +353,8 @@ class _Factory:
                 ("min", PropertyType.number, 0),
                 ("max", PropertyType.number, 100),
                 ("delta", PropertyType.dynamic_number),
+                ("delta_color", PropertyType.string),
+                ("negative_delta_color", PropertyType.string),
                 ("threshold", PropertyType.dynamic_number),
                 ("width", PropertyType.string_or_number),
                 ("height", PropertyType.string_or_number),
@@ -594,7 +596,7 @@ class _Factory:
             [
                 ("linear", PropertyType.boolean, False),
                 ("show_value", PropertyType.boolean, False),
-                ("render", PropertyType.dynamic_boolean, True)
+                ("render", PropertyType.dynamic_boolean, True),
             ]
         )
         ._set_propagate(),

+ 11 - 0
taipy/gui/viselements.json

@@ -1189,6 +1189,17 @@
                         "type": "dynamic(int|float)",
                         "doc": "The delta value to display."
                     },
+                    {
+                        "name": "delta_color",
+                        "type": "str",
+                        "doc": "The color that is used to display the value of the <i>delta</i> property. If negative_delta_color is set, then this property applies for positive values of delta only. If this property is set to \"invert\", then delta values are represented with the color used for negative values if delta is positive. The value for delta is also represented with the color used for positive values if delta is negative."
+                    },
+                    {
+                        "name": "negative_delta_color",
+                        "type": "str",
+                        "doc": "If set, this represents the color to be used when the value of <i>delta</i> is negative (or positive if <i>delta_color</i> is set to \"invert\")"
+
+                    },
                     {
                         "name": "threshold",
                         "type": "dynamic(int|float)",

+ 42 - 22
tests/gui/e2e/test_metric_indicator.py

@@ -102,8 +102,7 @@ def test_format_converter_integer_to_binary(page: Page, gui: Gui, helpers):
     helpers.run_e2e(gui)
     page.goto("./test")
     page.wait_for_selector(".plot-container")
-    number = page.locator(
-        "//*[@class='js-plotly-plot']//*[name()='svg'][2]//*[@class='number']")
+    number = page.locator("//*[@class='js-plotly-plot']//*[name()='svg'][2]//*[@class='number']")
     assert number.text_content() == "110010"
 
 
@@ -117,8 +116,7 @@ def test_format_converter_integer_to_signed_decimal_d_type(page: Page, gui: Gui,
     helpers.run_e2e(gui)
     page.goto("./test")
     page.wait_for_selector(".plot-container")
-    number = page.locator(
-        "//*[@class='js-plotly-plot']//*[name()='svg'][2]//*[@class='number']")
+    number = page.locator("//*[@class='js-plotly-plot']//*[name()='svg'][2]//*[@class='number']")
     assert number.text_content() == "50"
 
 
@@ -132,8 +130,7 @@ def test_format_converter_integer_to_signed_decimal_i_type(page: Page, gui: Gui,
     helpers.run_e2e(gui)
     page.goto("./test")
     page.wait_for_selector(".plot-container")
-    number = page.locator(
-        "//*[@class='js-plotly-plot']//*[name()='svg'][2]//*[@class='number']")
+    number = page.locator("//*[@class='js-plotly-plot']//*[name()='svg'][2]//*[@class='number']")
     assert number.text_content() == "50"
 
 
@@ -147,8 +144,7 @@ def test_format_converter_yields_float_using_science_notation(page: Page, gui: G
     helpers.run_e2e(gui)
     page.goto("./test")
     page.wait_for_selector(".plot-container")
-    number = page.locator(
-        "//*[@class='js-plotly-plot']//*[name()='svg'][2]//*[@class='number']")
+    number = page.locator("//*[@class='js-plotly-plot']//*[name()='svg'][2]//*[@class='number']")
     assert number.text_content() == "5.000000e+1"
 
 
@@ -162,8 +158,7 @@ def test_format_converter_yields_float_using_fixed_point_notation_f_type(page: P
     helpers.run_e2e(gui)
     page.goto("./test")
     page.wait_for_selector(".plot-container")
-    number = page.locator(
-        "//*[@class='js-plotly-plot']//*[name()='svg'][2]//*[@class='number']")
+    number = page.locator("//*[@class='js-plotly-plot']//*[name()='svg'][2]//*[@class='number']")
     assert number.text_content() == "99.99"
 
 
@@ -177,8 +172,7 @@ def test_format_converter_yields_float_using_fixed_point_notation_g_type(page: P
     helpers.run_e2e(gui)
     page.goto("./test")
     page.wait_for_selector(".plot-container")
-    number = page.locator(
-        "//*[@class='js-plotly-plot']//*[name()='svg'][2]//*[@class='number']")
+    number = page.locator("//*[@class='js-plotly-plot']//*[name()='svg'][2]//*[@class='number']")
     assert number.text_content() == "51"
 
 
@@ -192,8 +186,7 @@ def test_format_converter_yields_integer_as_octal(page: Page, gui: Gui, helpers)
     helpers.run_e2e(gui)
     page.goto("./test")
     page.wait_for_selector(".plot-container")
-    number = page.locator(
-        "//*[@class='js-plotly-plot']//*[name()='svg'][2]//*[@class='number']")
+    number = page.locator("//*[@class='js-plotly-plot']//*[name()='svg'][2]//*[@class='number']")
     assert number.text_content() == "62"
 
 
@@ -207,8 +200,7 @@ def test_format_converter_yields_integer_as_hexadecimal(page: Page, gui: Gui, he
     helpers.run_e2e(gui)
     page.goto("./test")
     page.wait_for_selector(".plot-container")
-    number = page.locator(
-        "//*[@class='js-plotly-plot']//*[name()='svg'][2]//*[@class='number']")
+    number = page.locator("//*[@class='js-plotly-plot']//*[name()='svg'][2]//*[@class='number']")
     assert number.text_content() == "32"
 
 
@@ -222,8 +214,7 @@ def test_format_converter_yields_integer_as_uppercase_hexadecimal(page: Page, gu
     helpers.run_e2e(gui)
     page.goto("./test")
     page.wait_for_selector(".plot-container")
-    number = page.locator(
-        "//*[@class='js-plotly-plot']//*[name()='svg'][2]//*[@class='number']")
+    number = page.locator("//*[@class='js-plotly-plot']//*[name()='svg'][2]//*[@class='number']")
     assert number.text_content() == "32"
 
 
@@ -237,8 +228,7 @@ def test_format_converter_yields_integer_as_unsigned_decimal(page: Page, gui: Gu
     helpers.run_e2e(gui)
     page.goto("./test")
     page.wait_for_selector(".plot-container")
-    number = page.locator(
-        "//*[@class='js-plotly-plot']//*[name()='svg'][2]//*[@class='number']")
+    number = page.locator("//*[@class='js-plotly-plot']//*[name()='svg'][2]//*[@class='number']")
     assert number.text_content() == "(50)"
 
 
@@ -252,9 +242,39 @@ def test_format_converter_yields_edge_cases(page: Page, gui: Gui, helpers):
     helpers.run_e2e(gui)
     page.goto("./test")
     page.wait_for_selector(".plot-container")
-    number = page.locator(
-        "//*[@class='js-plotly-plot']//*[name()='svg'][2]//*[@class='number']")
+    number = page.locator("//*[@class='js-plotly-plot']//*[name()='svg'][2]//*[@class='number']")
     assert number.text_content() == "a%b50c%d"
 
 
+@pytest.mark.teste2e
+def test_negative_delta_color(page: Page, gui: Gui, helpers):
+    page_md = """
+<|50|metric|negative_delta_color=#3D9970|delta=-20|>
+"""
+    gui._set_frame(inspect.currentframe())
+    gui.add_page(name="test", page=page_md)
+    helpers.run_e2e(gui)
+    page.goto("./test")
+    page.wait_for_selector(".plot-container")
+    events_list = page.locator("//*[@class='js-plotly-plot']//*[name()='svg'][2]//*[local-name()='text']")
+    delta = events_list.nth(1)
+    fill_color = delta.evaluate("el => getComputedStyle(el).fill")
+
+    assert fill_color == "rgb(61, 153, 112)"
+
+
+@pytest.mark.teste2e
+def test_delta_color_invert(page: Page, gui: Gui, helpers):
+    page_md = """
+<|50|metric|delta_color=invert|delta=-20|>
+"""
+    gui._set_frame(inspect.currentframe())
+    gui.add_page(name="test", page=page_md)
+    helpers.run_e2e(gui)
+    page.goto("./test")
+    page.wait_for_selector(".plot-container")
+    events_list = page.locator("//*[@class='js-plotly-plot']//*[name()='svg'][2]//*[local-name()='text']")
+    delta = events_list.nth(1)
+    fill_color = delta.evaluate("el => getComputedStyle(el).fill")
 
+    assert fill_color == "rgb(61, 153, 112)"