This is an automated email from the ASF dual-hosted git repository.

wusheng pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/skywalking-booster-ui.git


The following commit(s) were added to refs/heads/main by this push:
     new b37d65e  feat: enhance the legend of metrics graph widget with the 
summary table (#181)
b37d65e is described below

commit b37d65eaace09f19b196be71c52ebb43d4e226ba
Author: Fine0830 <[email protected]>
AuthorDate: Thu Nov 10 14:55:19 2022 +0800

    feat: enhance the legend of metrics graph widget with the summary table 
(#181)
---
 src/assets/icons/circle.svg                        |  15 ++
 src/components/Graph.vue                           |   1 +
 src/hooks/useLegendProcessor.ts                    | 142 +++++++++++++++++
 .../{useProcessor.ts => useMetricsProcessor.ts}    |   2 +-
 src/locales/lang/en.ts                             |  18 ++-
 src/locales/lang/es.ts                             |   8 +
 src/locales/lang/zh.ts                             |   8 +
 src/store/modules/topology.ts                      |   2 +-
 src/styles/lib.scss                                |   2 +-
 src/styles/reset.scss                              |   4 +
 src/types/dashboard.d.ts                           |  12 ++
 src/views/dashboard/configuration/Widget.vue       |   1 +
 .../configuration/widget/graph-styles/Area.vue     |   2 +
 .../configuration/widget/graph-styles/Bar.vue      |   2 +
 .../configuration/widget/graph-styles/Line.vue     |   6 +-
 .../widget/graph-styles/components/Legend.vue      | 138 ++++++++++++++++
 .../configuration/widget/metric/Index.vue          |   2 +-
 .../configuration/widget/metric/Standard.vue       |   2 +-
 src/views/dashboard/controls/Widget.vue            |   2 +-
 src/views/dashboard/graphs/Area.vue                |   3 +-
 src/views/dashboard/graphs/Bar.vue                 |  50 +++---
 src/views/dashboard/graphs/EndpointList.vue        |   5 +-
 src/views/dashboard/graphs/InstanceList.vue        |   5 +-
 src/views/dashboard/graphs/Line.vue                |  67 ++++----
 src/views/dashboard/graphs/ServiceList.vue         |   5 +-
 src/views/dashboard/graphs/components/Legend.vue   | 175 +++++++++++++++++++++
 .../related/topology/components/Graph.vue          |   4 +-
 .../related/topology/components/Sankey.vue         |   2 +-
 .../related/topology/components/Settings.vue       |   2 +-
 29 files changed, 595 insertions(+), 92 deletions(-)

diff --git a/src/assets/icons/circle.svg b/src/assets/icons/circle.svg
new file mode 100644
index 0000000..f2584ff
--- /dev/null
+++ b/src/assets/icons/circle.svg
@@ -0,0 +1,15 @@
+<!-- Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License. -->
+<svg t="1667899293763" class="icon" viewBox="0 0 1024 1024" version="1.1" 
xmlns="http://www.w3.org/2000/svg"; p-id="4705" width="16" height="16"><path 
d="M512 512m-368 0a368 368 0 1 0 736 0 368 368 0 1 0-736 0Z" 
p-id="4706"></path></svg>
\ No newline at end of file
diff --git a/src/components/Graph.vue b/src/components/Graph.vue
index 5bc8974..4a6523e 100644
--- a/src/components/Graph.vue
+++ b/src/components/Graph.vue
@@ -215,6 +215,7 @@ onBeforeUnmount(() => {
 
 .chart {
   overflow: hidden;
+  flex: 1;
 }
 
 .menus {
diff --git a/src/hooks/useLegendProcessor.ts b/src/hooks/useLegendProcessor.ts
new file mode 100644
index 0000000..ba6413d
--- /dev/null
+++ b/src/hooks/useLegendProcessor.ts
@@ -0,0 +1,142 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { LegendOptions } from "@/types/dashboard";
+import { isDef } from "@/utils/is";
+
+export default function useLegendProcess(legend?: LegendOptions) {
+  let isRight = false;
+  if (legend && legend.toTheRight) {
+    isRight = true;
+  }
+  function showEchartsLegend(keys: string[]) {
+    if (legend && isDef(legend.show)) {
+      if (legend.asTable && legend.show) {
+        return false;
+      }
+      return legend.show;
+    }
+    if (keys.length === 1) {
+      return false;
+    }
+    if (legend && legend.asTable) {
+      return false;
+    }
+    return true;
+  }
+  function aggregations(
+    data: { [key: string]: number[] },
+    intervalTime: string[]
+  ) {
+    const source: { [key: string]: unknown }[] = [];
+    const keys = Object.keys(data || {}).filter(
+      (i: any) => Array.isArray(data[i]) && data[i].length
+    );
+    const headers = [];
+
+    for (const [key, value] of keys.entries()) {
+      const arr = JSON.parse(JSON.stringify(data[value]));
+      const item: { [key: string]: unknown } = {
+        name: value,
+        topN: arr
+          .map((d: number, index: number) => {
+            return {
+              key: intervalTime[index],
+              value: d,
+            };
+          })
+          .sort(
+            (
+              a: { key: string; value: number },
+              b: { key: string; value: number }
+            ) => b.value - a.value
+          )
+          .filter((_: unknown, index: number) => index < 10),
+      };
+      if (legend) {
+        if (legend.min) {
+          item.min = Math.min(...data[value]).toFixed(2);
+          if (key === 0) {
+            headers.push({ value: "min", label: "Min" });
+          }
+        }
+        if (legend.max) {
+          item.max = Math.max(...data[value]).toFixed(2);
+          if (key === 0) {
+            headers.push({ value: "max", label: "Max" });
+          }
+        }
+        if (legend.mean) {
+          const total = data[value].reduce((prev: number, next: number) => {
+            prev += Number(next);
+            return prev;
+          }, 0);
+          item.mean = (total / data[value].length).toFixed(4);
+          if (key === 0) {
+            headers.push({ value: "mean", label: "Mean" });
+          }
+        }
+        if (legend.total) {
+          item.total = data[value]
+            .reduce((prev: number, next: number) => {
+              prev += Number(next);
+              return prev;
+            }, 0)
+            .toFixed(2);
+          if (key === 0) {
+            headers.push({ value: "total", label: "Total" });
+          }
+        }
+      }
+      source.push(item);
+    }
+
+    return { source, headers };
+  }
+  function chartColors(keys: string[]) {
+    let color: string[] = [];
+    switch (keys.length) {
+      case 2:
+        color = ["#FF6A84", "#a0b1e6"];
+        break;
+      case 1:
+        color = ["#3f96e3"];
+        break;
+      default:
+        color = [
+          "#30A4EB",
+          "#45BFC0",
+          "#FFCC55",
+          "#FF6A84",
+          "#a0a7e6",
+          "#c23531",
+          "#2f4554",
+          "#61a0a8",
+          "#d48265",
+          "#91c7ae",
+          "#749f83",
+          "#ca8622",
+          "#bda29a",
+          "#6e7074",
+          "#546570",
+          "#c4ccd3",
+        ];
+        break;
+    }
+    return color;
+  }
+  return { showEchartsLegend, isRight, aggregations, chartColors };
+}
diff --git a/src/hooks/useProcessor.ts b/src/hooks/useMetricsProcessor.ts
similarity index 99%
rename from src/hooks/useProcessor.ts
rename to src/hooks/useMetricsProcessor.ts
index 97b22e4..54dc3a1 100644
--- a/src/hooks/useProcessor.ts
+++ b/src/hooks/useMetricsProcessor.ts
@@ -184,7 +184,7 @@ export function useSourceProcessor(
     const c = (config.metricConfig && config.metricConfig[index]) || {};
 
     if (type === MetricQueryTypes.ReadMetricsValues) {
-      source[m] =
+      source[c.label || m] =
         (resp.data[keys[index]] &&
           calculateExp(resp.data[keys[index]].values.values, c)) ||
         [];
diff --git a/src/locales/lang/en.ts b/src/locales/lang/en.ts
index 0171ef0..7609aa0 100644
--- a/src/locales/lang/en.ts
+++ b/src/locales/lang/en.ts
@@ -52,17 +52,19 @@ const msg = {
   instance: "Instance",
   create: "Create",
   loading: "Loading",
-  selectVisualization: "Visualize your metrics",
+  selectVisualization: "Visualize Metrics",
   visualization: "Visualization",
-  graphStyles: "Graph styles",
-  widgetOptions: "Widget options",
-  standardOptions: "Standard options",
+  graphStyles: "Graph Styles",
+  widgetOptions: "Widget Options",
+  standardOptions: "Standard Options",
   max: "Max",
   min: "Min",
   plus: "Plus",
+  mean: "Mean",
   minus: "Minus",
   multiply: "Multiply",
   divide: "Divide",
+  total: "Total",
   convertToMilliseconds: "Convert Unix Timestamp(milliseconds)",
   convertToSeconds: "Convert Unix Timestamp(seconds)",
   smooth: "Smooth",
@@ -167,6 +169,11 @@ const msg = {
   enableRelatedTrace: "Enable Related Trace",
   maxTraceDuration: "Maximum Duration",
   minTraceDuration: "Minimum Duration",
+  legendOptions: "Legend Options",
+  showLegend: "Show Legend",
+  asTable: "As Table",
+  toTheRight: "To The Right",
+  legendValues: "Legend Values",
   seconds: "Seconds",
   hourTip: "Select Hour",
   minuteTip: "Select Minute",
@@ -259,7 +266,7 @@ const msg = {
   entityType: "Entity Type",
   maxItemNum: "Max number of Item",
   unknownMetrics: "Unknown Metrics",
-  labels: "Labels",
+  labels: "Label",
   aggregation: "Calculation",
   unit: "Unit",
   labelsIndex: "Label Subscript",
@@ -320,6 +327,7 @@ const msg = {
   eventsParameters: "Event Parameters",
   eventDetail: "Event Detail",
   value: "Value",
+  key: "Key",
   show: "Show",
   hide: "Hide",
   statistics: "Statistics",
diff --git a/src/locales/lang/es.ts b/src/locales/lang/es.ts
index 66fb561..cb743e6 100644
--- a/src/locales/lang/es.ts
+++ b/src/locales/lang/es.ts
@@ -59,9 +59,11 @@ const msg = {
   standardOptions: "Opciones estandar",
   max: "Máx",
   min: "Mín",
+  mean: "Promedio",
   plus: "Más",
   minus: "Menoss",
   multiply: "Multiplcar",
+  total: "Todo",
   divide: "Dividir",
   convertToMilliseconds: "Convertir Unix Timestamp(milisegundos)",
   convertToSeconds: "Convertir Unix Timestamp(segundos)",
@@ -160,6 +162,7 @@ const msg = {
   queryOrder: "Orden de consulta",
   latency: "Retraso",
   metricValues: "Valor métrico",
+  legendValues: "Valor de la leyenda",
   seconds: "Segundos",
   hourTip: "Seleccione Hora",
   minuteTip: "Seleccione Minuto",
@@ -171,6 +174,10 @@ const msg = {
   queryConditions: "Condiciones de consulta",
   maxTraceDuration: "Duración máxima",
   minTraceDuration: "Duración mínima",
+  legendOptions: "Opciones de leyenda",
+  showLegend: "Mostrar leyenda",
+  asTable: "Como tabla",
+  toTheRight: "Derecha",
   second: "s",
   yearSuffix: "Año",
   monthsHead: "Ene_Feb_Mar_Abr_May_Jun_Jul_Ago_Set_Oct_Nov_Dic",
@@ -320,6 +327,7 @@ const msg = {
   eventsParameters: "Parámetro del Evento",
   eventDetail: "Detalle del Evento",
   value: "Valor",
+  key: "Clave",
   show: "Mostrar",
   hide: "Oculatr",
   statistics: "Estadísticas",
diff --git a/src/locales/lang/zh.ts b/src/locales/lang/zh.ts
index 141e60d..aacde22 100644
--- a/src/locales/lang/zh.ts
+++ b/src/locales/lang/zh.ts
@@ -56,10 +56,12 @@ const msg = {
   standardOptions: "标准选项",
   max: "最大值",
   min: "最小值",
+  mean: "平均值",
   plus: "加法",
   minus: "减法",
   multiply: "乘法",
   divide: "除法",
+  total: "总计",
   convertToMilliseconds: "转换Unix时间戳(毫秒)",
   convertToSeconds: "转换Unix时间戳(秒)",
   smooth: "光滑的",
@@ -164,6 +166,11 @@ const msg = {
   queryConditions: "查询条件",
   maxTraceDuration: "最大持续时间",
   minTraceDuration: "最小持续时间",
+  legendOptions: "图例选项",
+  showLegend: "显示图例",
+  asTable: "作为表格",
+  toTheRight: "在右边",
+  legendValues: "图例值",
   seconds: "秒",
   hourTip: "选择小时",
   minuteTip: "选择分钟",
@@ -318,6 +325,7 @@ const msg = {
   eventsParameters: "事件参数",
   eventDetail: "事件详情",
   value: "数值",
+  key: "Key",
   tableHeader: "表头名称",
   tableValues: "表值",
   show: "展示",
diff --git a/src/store/modules/topology.ts b/src/store/modules/topology.ts
index c72a452..8b2205e 100644
--- a/src/store/modules/topology.ts
+++ b/src/store/modules/topology.ts
@@ -23,7 +23,7 @@ import { useSelectorStore } from "@/store/modules/selectors";
 import { useAppStoreWithOut } from "@/store/modules/app";
 import { AxiosResponse } from "axios";
 import query from "@/graphql/fetch";
-import { useQueryTopologyMetrics } from "@/hooks/useProcessor";
+import { useQueryTopologyMetrics } from "@/hooks/useMetricsProcessor";
 import { ElMessage } from "element-plus";
 
 interface MetricVal {
diff --git a/src/styles/lib.scss b/src/styles/lib.scss
index 1d2a508..f991ab4 100644
--- a/src/styles/lib.scss
+++ b/src/styles/lib.scss
@@ -173,7 +173,7 @@
 }
 
 .scroll_bar_style::-webkit-scrollbar {
-  width: 9px;
+  width: 4px;
   height: 4px;
   background-color: #eee;
 }
diff --git a/src/styles/reset.scss b/src/styles/reset.scss
index 06af9ce..41ef283 100644
--- a/src/styles/reset.scss
+++ b/src/styles/reset.scss
@@ -153,6 +153,10 @@ pre {
   margin-left: 5px;
 }
 
+.el-switch__label * {
+  font-size: 12px;
+}
+
 .el-drawer__header {
   margin-bottom: 0;
 }
diff --git a/src/types/dashboard.d.ts b/src/types/dashboard.d.ts
index ceabab3..a74d081 100644
--- a/src/types/dashboard.d.ts
+++ b/src/types/dashboard.d.ts
@@ -95,6 +95,7 @@ export type GraphConfig =
 export interface BarConfig {
   type?: string;
   showBackground?: boolean;
+  legend?: LegendOptions;
 }
 export interface LineConfig extends AreaConfig {
   type?: string;
@@ -110,6 +111,7 @@ export interface LineConfig extends AreaConfig {
 export interface AreaConfig {
   type?: string;
   opacity?: number;
+  legend?: LegendOptions;
 }
 
 export interface CardConfig {
@@ -180,3 +182,13 @@ export type EventParams = {
   value: number | number[];
   color: string;
 };
+export type LegendOptions = {
+  show: boolean;
+  total: boolean;
+  min: boolean;
+  max: boolean;
+  mean: boolean;
+  asTable: boolean;
+  toTheRight: boolean;
+  width: number;
+};
diff --git a/src/views/dashboard/configuration/Widget.vue 
b/src/views/dashboard/configuration/Widget.vue
index 4582b70..c09a4c4 100644
--- a/src/views/dashboard/configuration/Widget.vue
+++ b/src/views/dashboard/configuration/Widget.vue
@@ -32,6 +32,7 @@ limitations under the License. -->
           :data="states.source"
           :config="{
             ...graph,
+            legend: (dashboardStore.selectedGrid.graph || {}).legend,
             i: dashboardStore.selectedGrid.i,
             metrics: dashboardStore.selectedGrid.metrics,
             metricTypes: dashboardStore.selectedGrid.metricTypes,
diff --git a/src/views/dashboard/configuration/widget/graph-styles/Area.vue 
b/src/views/dashboard/configuration/widget/graph-styles/Area.vue
index 9c4b3e0..620c803 100644
--- a/src/views/dashboard/configuration/widget/graph-styles/Area.vue
+++ b/src/views/dashboard/configuration/widget/graph-styles/Area.vue
@@ -13,6 +13,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 
or implied.
 See the License for the specific language governing permissions and
 limitations under the License. -->
 <template>
+  <Legend />
   <div>
     <span class="label">{{ t("areaOpacity") }}</span>
     <el-slider
@@ -31,6 +32,7 @@ limitations under the License. -->
 import { ref } from "vue";
 import { useI18n } from "vue-i18n";
 import { useDashboardStore } from "@/store/modules/dashboard";
+import Legend from "./components/Legend.vue";
 
 const { t } = useI18n();
 const dashboardStore = useDashboardStore();
diff --git a/src/views/dashboard/configuration/widget/graph-styles/Bar.vue 
b/src/views/dashboard/configuration/widget/graph-styles/Bar.vue
index 0cffd9e..e526847 100644
--- a/src/views/dashboard/configuration/widget/graph-styles/Bar.vue
+++ b/src/views/dashboard/configuration/widget/graph-styles/Bar.vue
@@ -13,6 +13,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 
or implied.
 See the License for the specific language governing permissions and
 limitations under the License. -->
 <template>
+  <Legend />
   <div>
     <span class="label">{{ t("showBackground") }}</span>
     <el-switch
@@ -27,6 +28,7 @@ limitations under the License. -->
 import { ref } from "vue";
 import { useDashboardStore } from "@/store/modules/dashboard";
 import { useI18n } from "vue-i18n";
+import Legend from "./components/Legend.vue";
 
 const { t } = useI18n();
 const dashboardStore = useDashboardStore();
diff --git a/src/views/dashboard/configuration/widget/graph-styles/Line.vue 
b/src/views/dashboard/configuration/widget/graph-styles/Line.vue
index 66b7c78..de05726 100644
--- a/src/views/dashboard/configuration/widget/graph-styles/Line.vue
+++ b/src/views/dashboard/configuration/widget/graph-styles/Line.vue
@@ -13,6 +13,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 
or implied.
 See the License for the specific language governing permissions and
 limitations under the License. -->
 <template>
+  <Legend />
   <div>
     <span class="label">{{ t("showXAxis") }}</span>
     <el-switch
@@ -63,6 +64,7 @@ limitations under the License. -->
 import { ref, computed } from "vue";
 import { useI18n } from "vue-i18n";
 import { useDashboardStore } from "@/store/modules/dashboard";
+import Legend from "./components/Legend.vue";
 
 const { t } = useI18n();
 const dashboardStore = useDashboardStore();
@@ -82,8 +84,8 @@ function updateConfig(param: { [key: string]: unknown }) {
 <style lang="scss" scoped>
 .label {
   font-size: 13px;
-  font-weight: 500;
   display: block;
-  margin-bottom: 5px;
+  margin-top: 5px;
+  margin-bottom: -5px;
 }
 </style>
diff --git 
a/src/views/dashboard/configuration/widget/graph-styles/components/Legend.vue 
b/src/views/dashboard/configuration/widget/graph-styles/components/Legend.vue
new file mode 100644
index 0000000..9efea04
--- /dev/null
+++ 
b/src/views/dashboard/configuration/widget/graph-styles/components/Legend.vue
@@ -0,0 +1,138 @@
+<!-- Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License. -->
+<template>
+  <div>
+    <span class="label mr-5">{{ t("showLegend") }}</span>
+    <el-switch
+      v-model="legend.show"
+      active-text="Yes"
+      inactive-text="No"
+      @change="updateLegendConfig({ show: legend.show })"
+    />
+  </div>
+  <div>
+    <span class="label">{{ t("asTable") }}</span>
+    <el-switch
+      v-model="legend.asTable"
+      active-text="Yes"
+      inactive-text="No"
+      @change="updateLegendConfig({ asTable: legend.asTable })"
+    />
+  </div>
+  <div v-show="legend.asTable">
+    <span class="label">{{ t("legendOptions") }}</span>
+    <span class="title mr-5">{{ t("toTheRight") }}</span>
+    <el-switch
+      v-model="legend.toTheRight"
+      active-text="Yes"
+      inactive-text="No"
+      @change="updateLegendConfig({ toTheRight: legend.toTheRight })"
+    />
+    <span class="title ml-20 mr-5">{{ t("width") }}</span>
+    <el-input
+      v-model="legend.width"
+      class="inputs"
+      size="small"
+      placeholder="Please input the width"
+      @change="updateLegendConfig({ width: legend.width })"
+    />
+  </div>
+  <div v-show="legend.asTable">
+    <span class="label">{{ t("legendValues") }}</span>
+    <span class="title mr-5">{{ t("min") }}</span>
+    <el-switch
+      v-model="legend.min"
+      active-text="Yes"
+      inactive-text="No"
+      @change="updateLegendConfig({ min: legend.min })"
+    />
+    <span class="title ml-20 mr-5">{{ t("max") }}</span>
+    <el-switch
+      v-model="legend.max"
+      active-text="Yes"
+      inactive-text="No"
+      @change="updateLegendConfig({ max: legend.max })"
+    />
+    <span class="title ml-20 mr-5">{{ t("mean") }}</span>
+    <el-switch
+      v-model="legend.mean"
+      active-text="Yes"
+      inactive-text="No"
+      @change="updateLegendConfig({ mean: legend.mean })"
+    />
+    <span class="title ml-20 mr-5">{{ t("total") }}</span>
+    <el-switch
+      v-model="legend.total"
+      active-text="Yes"
+      inactive-text="No"
+      @change="updateLegendConfig({ total: legend.total })"
+    />
+  </div>
+</template>
+<script lang="ts" setup>
+import { computed, reactive } from "vue";
+import { useI18n } from "vue-i18n";
+import { useDashboardStore } from "@/store/modules/dashboard";
+import { LegendOptions } from "@/types/dashboard";
+
+const { t } = useI18n();
+const dashboardStore = useDashboardStore();
+const graph = computed(() => dashboardStore.selectedGrid.graph || {});
+const legend = reactive<LegendOptions>({
+  show: true,
+  total: false,
+  min: false,
+  max: false,
+  mean: false,
+  asTable: false,
+  toTheRight: false,
+  width: 130,
+  ...graph.value.legend,
+});
+
+function updateLegendConfig(param: { [key: string]: unknown }) {
+  const g = {
+    ...dashboardStore.selectedGrid.graph,
+    legend: {
+      ...dashboardStore.selectedGrid.graph.legend,
+      ...param,
+    },
+  };
+  dashboardStore.selectWidget({
+    ...dashboardStore.selectedGrid,
+    graph: g,
+  });
+}
+</script>
+<style lang="scss" scoped>
+.label {
+  font-size: 13px;
+  display: block;
+  margin-top: 5px;
+  margin-bottom: -5px;
+}
+
+.title {
+  font-size: 12px;
+  display: inline-flex;
+  height: 32px;
+  line-height: 34px;
+  vertical-align: middle;
+}
+
+.inputs {
+  width: 120px;
+}
+</style>
diff --git a/src/views/dashboard/configuration/widget/metric/Index.vue 
b/src/views/dashboard/configuration/widget/metric/Index.vue
index cdb31f0..6c66f05 100644
--- a/src/views/dashboard/configuration/widget/metric/Index.vue
+++ b/src/views/dashboard/configuration/widget/metric/Index.vue
@@ -113,7 +113,7 @@ import {
   useQueryProcessor,
   useSourceProcessor,
   useGetMetricEntity,
-} from "@/hooks/useProcessor";
+} from "@/hooks/useMetricsProcessor";
 import { useI18n } from "vue-i18n";
 import { DashboardItem, MetricConfigOpt } from "@/types/dashboard";
 import Standard from "./Standard.vue";
diff --git a/src/views/dashboard/configuration/widget/metric/Standard.vue 
b/src/views/dashboard/configuration/widget/metric/Standard.vue
index 1ba282c..213baf4 100644
--- a/src/views/dashboard/configuration/widget/metric/Standard.vue
+++ b/src/views/dashboard/configuration/widget/metric/Standard.vue
@@ -122,7 +122,7 @@ const hasLabel = computed(() => {
   const graph = dashboardStore.selectedGrid.graph || {};
   return (
     ListChartTypes.includes(graph.type) ||
-    metricType.value === "readLabeledMetricsValues"
+    ["readLabeledMetricsValues", 
"readMetricsValues"].includes(metricType.value)
   );
 });
 const isTopn = computed(() =>
diff --git a/src/views/dashboard/controls/Widget.vue 
b/src/views/dashboard/controls/Widget.vue
index 8593d43..cccd42f 100644
--- a/src/views/dashboard/controls/Widget.vue
+++ b/src/views/dashboard/controls/Widget.vue
@@ -86,7 +86,7 @@ import {
   useQueryProcessor,
   useSourceProcessor,
   useGetMetricEntity,
-} from "@/hooks/useProcessor";
+} from "@/hooks/useMetricsProcessor";
 import { EntityType, ListChartTypes } from "../data";
 import { EventParams } from "@/types/dashboard";
 import getDashboard from "@/hooks/useDashboardsSession";
diff --git a/src/views/dashboard/graphs/Area.vue 
b/src/views/dashboard/graphs/Area.vue
index 46927f9..5e4336c 100644
--- a/src/views/dashboard/graphs/Area.vue
+++ b/src/views/dashboard/graphs/Area.vue
@@ -44,7 +44,8 @@ defineProps({
       AreaConfig & {
         filters: Filters;
         relatedTrace: RelatedTrace;
-      } & { id: string }
+        id: string;
+      }
     >,
     default: () => ({}),
   },
diff --git a/src/views/dashboard/graphs/Bar.vue 
b/src/views/dashboard/graphs/Bar.vue
index b236d28..ad52e48 100644
--- a/src/views/dashboard/graphs/Bar.vue
+++ b/src/views/dashboard/graphs/Bar.vue
@@ -13,7 +13,10 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 
or implied.
 See the License for the specific language governing permissions and
 limitations under the License. -->
 <template>
-  <Graph :option="option" @select="clickEvent" :filters="config.filters" />
+  <div class="graph" :class="isRight ? 'flex-h' : 'flex-v'">
+    <Graph :option="option" @select="clickEvent" :filters="config.filters" />
+    <Legend :config="config.legend" :data="data" :intervalTime="intervalTime" 
/>
+  </div>
 </template>
 <script lang="ts" setup>
 import { computed } from "vue";
@@ -24,6 +27,7 @@ import {
   RelatedTrace,
   Filters,
 } from "@/types/dashboard";
+import useLegendProcess from "@/hooks/useLegendProcessor";
 
 /*global defineProps, defineEmits */
 const emits = defineEmits(["click"]);
@@ -39,11 +43,15 @@ const props = defineProps({
       BarConfig & {
         filters: Filters;
         relatedTrace: RelatedTrace;
-      } & { id: string }
+        id: string;
+      }
     >,
     default: () => ({}),
   },
 });
+const { showEchartsLegend, isRight, chartColors } = useLegendProcess(
+  props.config.legend
+);
 const option = computed(() => getOption());
 
 function getOption() {
@@ -73,35 +81,7 @@ function getOption() {
       },
     };
   });
-  let color: string[] = [];
-  switch (keys.length) {
-    case 2:
-      color = ["#FF6A84", "#a0b1e6"];
-      break;
-    case 1:
-      color = ["#3f96e3"];
-      break;
-    default:
-      color = [
-        "#30A4EB",
-        "#45BFC0",
-        "#FFCC55",
-        "#FF6A84",
-        "#a0a7e6",
-        "#c23531",
-        "#2f4554",
-        "#61a0a8",
-        "#d48265",
-        "#91c7ae",
-        "#749f83",
-        "#ca8622",
-        "#bda29a",
-        "#6e7074",
-        "#546570",
-        "#c4ccd3",
-      ];
-      break;
-  }
+  const color: string[] = chartColors(keys);
   return {
     color,
     tooltip: {
@@ -114,7 +94,7 @@ function getOption() {
     },
     legend: {
       type: "scroll",
-      show: keys.length === 1 ? false : true,
+      show: showEchartsLegend(keys),
       icon: "circle",
       top: 0,
       left: 0,
@@ -160,3 +140,9 @@ function clickEvent(params: EventParams) {
   emits("click", params);
 }
 </script>
+<style lang="scss" scoped>
+.graph {
+  width: 100%;
+  height: 100%;
+}
+</style>
diff --git a/src/views/dashboard/graphs/EndpointList.vue 
b/src/views/dashboard/graphs/EndpointList.vue
index 138865c..8209ee6 100644
--- a/src/views/dashboard/graphs/EndpointList.vue
+++ b/src/views/dashboard/graphs/EndpointList.vue
@@ -66,7 +66,10 @@ import type { PropType } from "vue";
 import { EndpointListConfig } from "@/types/dashboard";
 import { Endpoint } from "@/types/selector";
 import { useDashboardStore } from "@/store/modules/dashboard";
-import { useQueryPodsMetrics, usePodsSource } from "@/hooks/useProcessor";
+import {
+  useQueryPodsMetrics,
+  usePodsSource,
+} from "@/hooks/useMetricsProcessor";
 import { EntityType } from "../data";
 import router from "@/router";
 import getDashboard from "@/hooks/useDashboardsSession";
diff --git a/src/views/dashboard/graphs/InstanceList.vue 
b/src/views/dashboard/graphs/InstanceList.vue
index 2963102..ef171dd 100644
--- a/src/views/dashboard/graphs/InstanceList.vue
+++ b/src/views/dashboard/graphs/InstanceList.vue
@@ -95,7 +95,10 @@ import { useSelectorStore } from "@/store/modules/selectors";
 import { useDashboardStore } from "@/store/modules/dashboard";
 import { InstanceListConfig } from "@/types/dashboard";
 import { Instance } from "@/types/selector";
-import { useQueryPodsMetrics, usePodsSource } from "@/hooks/useProcessor";
+import {
+  useQueryPodsMetrics,
+  usePodsSource,
+} from "@/hooks/useMetricsProcessor";
 import { EntityType } from "../data";
 import router from "@/router";
 import getDashboard from "@/hooks/useDashboardsSession";
diff --git a/src/views/dashboard/graphs/Line.vue 
b/src/views/dashboard/graphs/Line.vue
index ae27ff7..c26c6b1 100644
--- a/src/views/dashboard/graphs/Line.vue
+++ b/src/views/dashboard/graphs/Line.vue
@@ -13,15 +13,18 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 
express or implied.
 See the License for the specific language governing permissions and
 limitations under the License. -->
 <template>
-  <Graph
-    :option="option"
-    @select="clickEvent"
-    :filters="config.filters"
-    :relatedTrace="config.relatedTrace"
-  />
+  <div class="graph flex-v" :class="setRight ? 'flex-h' : 'flex-v'">
+    <Graph
+      :option="option"
+      @select="clickEvent"
+      :filters="config.filters"
+      :relatedTrace="config.relatedTrace"
+    />
+    <Legend :config="config.legend" :data="data" :intervalTime="intervalTime" 
/>
+  </div>
 </template>
 <script lang="ts" setup>
-import { computed } from "vue";
+import { computed, ref } from "vue";
 import type { PropType } from "vue";
 import {
   LineConfig,
@@ -29,6 +32,8 @@ import {
   RelatedTrace,
   Filters,
 } from "@/types/dashboard";
+import Legend from "./components/Legend.vue";
+import useLegendProcess from "@/hooks/useLegendProcessor";
 
 /*global defineProps, defineEmits */
 const emits = defineEmits(["click"]);
@@ -44,7 +49,8 @@ const props = defineProps({
       LineConfig & {
         filters?: Filters;
         relatedTrace?: RelatedTrace;
-      } & { id?: string }
+        id?: string;
+      }
     >,
     default: () => ({
       step: false,
@@ -58,8 +64,13 @@ const props = defineProps({
     }),
   },
 });
+const setRight = ref<boolean>(false);
 const option = computed(() => getOption());
 function getOption() {
+  const { showEchartsLegend, isRight, chartColors } = useLegendProcess(
+    props.config.legend
+  );
+  setRight.value = isRight;
   const keys = Object.keys(props.data || {}).filter(
     (i: any) => Array.isArray(props.data[i]) && props.data[i].length
   );
@@ -88,35 +99,7 @@ function getOption() {
     }
     return serie;
   });
-  let color: string[] = [];
-  switch (keys.length) {
-    case 2:
-      color = ["#FF6A84", "#a0b1e6"];
-      break;
-    case 1:
-      color = ["#3f96e3"];
-      break;
-    default:
-      color = [
-        "#30A4EB",
-        "#45BFC0",
-        "#FFCC55",
-        "#FF6A84",
-        "#a0a7e6",
-        "#c23531",
-        "#2f4554",
-        "#61a0a8",
-        "#d48265",
-        "#91c7ae",
-        "#749f83",
-        "#ca8622",
-        "#bda29a",
-        "#6e7074",
-        "#546570",
-        "#c4ccd3",
-      ];
-      break;
-  }
+  const color: string[] = chartColors(keys);
   const tooltip = {
     trigger: "none",
     axisPointer: {
@@ -151,7 +134,7 @@ function getOption() {
     tooltip: props.config.smallTips ? tips : tooltip,
     legend: {
       type: "scroll",
-      show: keys.length === 1 ? false : true,
+      show: showEchartsLegend(keys),
       icon: "circle",
       top: 0,
       left: 0,
@@ -167,7 +150,7 @@ function getOption() {
       },
     },
     grid: {
-      top: keys.length === 1 ? 15 : 55,
+      top: showEchartsLegend(keys) ? 35 : 10,
       left: 0,
       right: 10,
       bottom: 5,
@@ -205,3 +188,9 @@ function clickEvent(params: EventParams) {
   emits("click", params);
 }
 </script>
+<style lang="scss" scoped>
+.graph {
+  width: 100%;
+  height: 100%;
+}
+</style>
diff --git a/src/views/dashboard/graphs/ServiceList.vue 
b/src/views/dashboard/graphs/ServiceList.vue
index 3a59762..4409181 100644
--- a/src/views/dashboard/graphs/ServiceList.vue
+++ b/src/views/dashboard/graphs/ServiceList.vue
@@ -93,7 +93,10 @@ import { useSelectorStore } from "@/store/modules/selectors";
 import { useDashboardStore } from "@/store/modules/dashboard";
 import { useAppStoreWithOut } from "@/store/modules/app";
 import { Service } from "@/types/selector";
-import { useQueryPodsMetrics, usePodsSource } from "@/hooks/useProcessor";
+import {
+  useQueryPodsMetrics,
+  usePodsSource,
+} from "@/hooks/useMetricsProcessor";
 import { EntityType } from "../data";
 import router from "@/router";
 import getDashboard from "@/hooks/useDashboardsSession";
diff --git a/src/views/dashboard/graphs/components/Legend.vue 
b/src/views/dashboard/graphs/components/Legend.vue
new file mode 100644
index 0000000..b9839be
--- /dev/null
+++ b/src/views/dashboard/graphs/components/Legend.vue
@@ -0,0 +1,175 @@
+<!-- Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License. -->
+<template>
+  <div
+    v-if="tableData.length && config.asTable"
+    role="region"
+    aria-labelledby="caption"
+    tabindex="0"
+    :style="`width: ${width}; maxHeight:${isRight ? '100%' : 130}`"
+    class="scroll_bar_style"
+  >
+    <table>
+      <thead>
+        <tr>
+          <th></th>
+          <th v-for="h in headerRow" :key="h.value">
+            {{ h.label }}
+          </th>
+        </tr>
+      </thead>
+      <tbody>
+        <tr v-for="(item, index) in tableData" :key="index">
+          <th>
+            <el-popover placement="bottom" :width="230" trigger="click">
+              <template #reference>
+                <div class="name">
+                  <Icon iconName="circle" :style="`color: ${colors[index]};`" 
/>
+                  <i>{{ item.name }}</i>
+                </div>
+              </template>
+              <div class="list">
+                <div class="value">
+                  <span>{{ t("key") }}</span>
+                  <span>{{ t("value") }}</span>
+                </div>
+                <div class="value" v-for="(d, index) in item.topN" 
:key="index">
+                  <span>{{ d.key }}</span>
+                  <span>{{ d.value }}</span>
+                </div>
+              </div>
+            </el-popover>
+          </th>
+          <td v-for="h in headerRow" :key="h.value">
+            {{ item[h.value] }}
+          </td>
+        </tr>
+      </tbody>
+    </table>
+  </div>
+</template>
+<script lang="ts" setup>
+import { computed } from "vue";
+import type { PropType } from "vue";
+import { useI18n } from "vue-i18n";
+import { LegendOptions } from "@/types/dashboard";
+import useLegendProcess from "@/hooks/useLegendProcessor";
+
+/*global defineProps */
+const props = defineProps({
+  data: {
+    type: Object as PropType<{ [key: string]: number[] }>,
+    default: () => ({}),
+  },
+  config: {
+    type: Object as PropType<LegendOptions>,
+    default: () => ({}),
+  },
+  intervalTime: { type: Array as PropType<string[]>, default: () => [] },
+});
+const { t } = useI18n();
+const tableData: any = computed(() => {
+  const { aggregations } = useLegendProcess(props.config);
+  return aggregations(props.data, props.intervalTime).source;
+});
+const headerRow = computed(() => {
+  const { aggregations } = useLegendProcess(props.config);
+  return aggregations(props.data, props.intervalTime).headers;
+});
+const isRight = computed(() => useLegendProcess(props.config).isRight);
+const width = computed(() =>
+  props.config.width
+    ? props.config.width + "px"
+    : isRight.value
+    ? "150px"
+    : "100%"
+);
+const colors = computed(() => {
+  const keys = Object.keys(props.data || {}).filter(
+    (i: any) => Array.isArray(props.data[i]) && props.data[i].length
+  );
+  const { chartColors } = useLegendProcess(props.config);
+  return chartColors(keys);
+});
+</script>
+<style lang="scss" scoped>
+table {
+  font-size: 12px;
+  white-space: nowrap;
+  margin: 0;
+  border: none;
+  border-collapse: separate;
+  border-spacing: 0;
+  table-layout: fixed;
+}
+
+table th {
+  padding: 5px;
+}
+
+table thead th {
+  position: sticky;
+  top: 0;
+  z-index: 1;
+  width: 25vw;
+  background: #fff;
+}
+
+.name {
+  cursor: pointer;
+}
+
+table td {
+  padding: 5px;
+  text-align: center;
+}
+
+table thead th:first-child {
+  position: sticky;
+  left: 0;
+  z-index: 2;
+}
+
+table tbody th {
+  font-weight: bold;
+  font-style: italic;
+  text-align: left;
+  background: #fff;
+  position: sticky;
+  left: 0;
+  z-index: 1;
+}
+
+[role="region"][aria-labelledby][tabindex] {
+  overflow: auto;
+}
+
+i {
+  font-style: normal;
+}
+
+.value {
+  span {
+    display: inline-block;
+    padding: 5px;
+    width: 80px;
+  }
+}
+
+.list {
+  height: 360px;
+  overflow: auto;
+}
+</style>
diff --git a/src/views/dashboard/related/topology/components/Graph.vue 
b/src/views/dashboard/related/topology/components/Graph.vue
index 6a319b5..29e7ce9 100644
--- a/src/views/dashboard/related/topology/components/Graph.vue
+++ b/src/views/dashboard/related/topology/components/Graph.vue
@@ -111,9 +111,9 @@ import { Service } from "@/types/selector";
 import { useAppStoreWithOut } from "@/store/modules/app";
 import getDashboard from "@/hooks/useDashboardsSession";
 import { MetricConfigOpt } from "@/types/dashboard";
-import { aggregation } from "@/hooks/useProcessor";
+import { aggregation } from "@/hooks/useMetricsProcessor";
 import icons from "@/assets/img/icons";
-import { useQueryTopologyMetrics } from "@/hooks/useProcessor";
+import { useQueryTopologyMetrics } from "@/hooks/useMetricsProcessor";
 
 /*global Nullable, defineProps */
 const props = defineProps({
diff --git a/src/views/dashboard/related/topology/components/Sankey.vue 
b/src/views/dashboard/related/topology/components/Sankey.vue
index afa86d1..7c091fc 100644
--- a/src/views/dashboard/related/topology/components/Sankey.vue
+++ b/src/views/dashboard/related/topology/components/Sankey.vue
@@ -21,7 +21,7 @@ import { computed, PropType } from "vue";
 import { useTopologyStore } from "@/store/modules/topology";
 import { Node, Call } from "@/types/topology";
 import { MetricConfigOpt } from "@/types/dashboard";
-import { aggregation } from "@/hooks/useProcessor";
+import { aggregation } from "@/hooks/useMetricsProcessor";
 
 /*global defineEmits, defineProps */
 const props = defineProps({
diff --git a/src/views/dashboard/related/topology/components/Settings.vue 
b/src/views/dashboard/related/topology/components/Settings.vue
index 0473510..9af3022 100644
--- a/src/views/dashboard/related/topology/components/Settings.vue
+++ b/src/views/dashboard/related/topology/components/Settings.vue
@@ -248,7 +248,7 @@ import { useTopologyStore } from "@/store/modules/topology";
 import { ElMessage } from "element-plus";
 import { MetricCatalog, ScopeType, MetricConditions } from "../../../data";
 import { Option } from "@/types/app";
-import { useQueryTopologyMetrics } from "@/hooks/useProcessor";
+import { useQueryTopologyMetrics } from "@/hooks/useMetricsProcessor";
 import { Node } from "@/types/topology";
 import { DashboardItem, MetricConfigOpt } from "@/types/dashboard";
 import { EntityType, LegendOpt, MetricsType } from "../../../data";


Reply via email to