refactor: optimize graph tooltips to make them more friendly (#216)

This commit is contained in:
Fine0830 2023-01-05 17:44:28 +08:00 committed by GitHub
parent 8785817efe
commit 163de5e5cf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 52 additions and 49 deletions

View File

@ -15,7 +15,15 @@ limitations under the License. -->
<template> <template>
<div class="chart" ref="chartRef" :style="`height:${height};width:${width};`"> <div class="chart" ref="chartRef" :style="`height:${height};width:${width};`">
<div v-if="!available" class="no-data">No Data</div> <div v-if="!available" class="no-data">No Data</div>
<div class="menus" v-show="visMenus" ref="menus"> <div
class="menus"
v-show="visMenus"
:style="{
top: menuPos.y + 'px',
left: menuPos.x + 'px',
}"
@mouseenter="hideTooltips"
>
<div class="tools" @click="associateMetrics" v-if="associate.length"> <div class="tools" @click="associateMetrics" v-if="associate.length">
{{ t("associateMetrics") }} {{ t("associateMetrics") }}
</div> </div>
@ -36,7 +44,7 @@ limitations under the License. -->
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { watch, ref, onMounted, onBeforeUnmount, unref, computed } from "vue"; import { watch, ref, onMounted, onBeforeUnmount, unref, computed, reactive } from "vue";
import type { PropType, Ref } from "vue"; import type { PropType, Ref } from "vue";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
import type { EventParams } from "@/types/app"; import type { EventParams } from "@/types/app";
@ -50,7 +58,6 @@ limitations under the License. -->
const emits = defineEmits(["select"]); const emits = defineEmits(["select"]);
const { t } = useI18n(); const { t } = useI18n();
const chartRef = ref<Nullable<HTMLDivElement>>(null); const chartRef = ref<Nullable<HTMLDivElement>>(null);
const menus = ref<Nullable<HTMLDivElement>>(null);
const visMenus = ref<boolean>(false); const visMenus = ref<boolean>(false);
const { setOptions, resize, getInstance } = useECharts(chartRef as Ref<HTMLDivElement>); const { setOptions, resize, getInstance } = useECharts(chartRef as Ref<HTMLDivElement>);
const currentParams = ref<Nullable<EventParams>>(null); const currentParams = ref<Nullable<EventParams>>(null);
@ -58,6 +65,7 @@ limitations under the License. -->
const traceOptions = ref<{ type: string; filters?: unknown }>({ const traceOptions = ref<{ type: string; filters?: unknown }>({
type: "Trace", type: "Trace",
}); });
const menuPos = reactive<{ x: number; y: number }>({ x: NaN, y: NaN });
const props = defineProps({ const props = defineProps({
height: { type: String, default: "100%" }, height: { type: String, default: "100%" },
width: { type: String, default: "100%" }, width: { type: String, default: "100%" },
@ -100,26 +108,37 @@ limitations under the License. -->
emits("select", currentParams.value); emits("select", currentParams.value);
return; return;
} }
if (!menus.value || !chartRef.value) { instance.dispatchAction({
type: "hideTip",
});
visMenus.value = true;
if (!chartRef.value) {
return; return;
} }
visMenus.value = true;
const w = chartRef.value.getBoundingClientRect().width || 0; const w = chartRef.value.getBoundingClientRect().width || 0;
const h = chartRef.value.getBoundingClientRect().height || 0; const h = chartRef.value.getBoundingClientRect().height || 0;
if (w - params.event.offsetX > 120) { if (w - params.event.offsetX > 120) {
menus.value.style.left = params.event.offsetX + "px"; menuPos.x = params.event.offsetX;
} else { } else {
menus.value.style.left = params.event.offsetX - 120 + "px"; menuPos.x = params.event.offsetX - 120;
} }
if (h - params.event.offsetY < 50) { if (h - params.event.offsetY < 50) {
menus.value.style.top = params.event.offsetY - 40 + "px"; menuPos.y = params.event.offsetY - 40;
} else { } else {
menus.value.style.top = params.event.offsetY + 2 + "px"; menuPos.y = params.event.offsetY;
} }
}); });
if (props.option.series.type === "sankey") { if (props.option.series.type === "sankey") {
return; return;
} }
instance.on("mouseover", () => {
visMenus.value = false;
});
instance.on("mouseout", () => {
instance.dispatchAction({
type: "hideTip",
});
});
document.addEventListener( document.addEventListener(
"click", "click",
() => { () => {
@ -127,6 +146,9 @@ limitations under the License. -->
return; return;
} }
visMenus.value = false; visMenus.value = false;
instance.dispatchAction({
type: "hideTip",
});
instance.dispatchAction({ instance.dispatchAction({
type: "updateAxisPointer", type: "updateAxisPointer",
currTrigger: "leave", currTrigger: "leave",
@ -139,14 +161,10 @@ limitations under the License. -->
function associateMetrics() { function associateMetrics() {
emits("select", currentParams.value); emits("select", currentParams.value);
const { dataIndex, seriesIndex } = currentParams.value || { updateOptions(currentParams.value || undefined);
dataIndex: 0,
seriesIndex: 0,
};
updateOptions({ dataIndex, seriesIndex });
} }
function updateOptions(params?: { dataIndex: number; seriesIndex: number }) { function updateOptions(params?: EventParams) {
const instance = getInstance(); const instance = getInstance();
if (!instance) { if (!instance) {
return; return;
@ -160,16 +178,10 @@ limitations under the License. -->
setOptions(options || props.option); setOptions(options || props.option);
} else { } else {
instance.dispatchAction({ instance.dispatchAction({
type: "updateAxisPointer", type: "showTip",
dataIndex: params ? params.dataIndex : props.filters.dataIndex, dataIndex: params ? params.dataIndex : props.filters.dataIndex,
seriesIndex: params ? params.seriesIndex : 0, seriesIndex: params ? params.seriesIndex : 0,
}); });
const ids = props.option.series.map((_: unknown, index: number) => index);
instance.dispatchAction({
type: "highlight",
dataIndex: params ? params.dataIndex : props.filters.dataIndex,
seriesIndex: ids,
});
} }
} }
@ -183,6 +195,13 @@ limitations under the License. -->
visMenus.value = true; visMenus.value = true;
} }
function hideTooltips() {
const instance = getInstance();
instance.dispatchAction({
type: "hideTip",
});
}
watch( watch(
() => props.option, () => props.option,
(newVal, oldVal) => { (newVal, oldVal) => {

View File

@ -74,12 +74,14 @@ limitations under the License. -->
return { return {
color, color,
tooltip: { tooltip: {
trigger: "none", trigger: "axis",
axisPointer: { textStyle: {
type: "cross", fontSize: 12,
color: "#333", color: "#333",
backgroundColor: "rgba(255, 255, 255, 0.8)",
}, },
enterable: true,
confine: true,
extraCssText: "max-height: 300px; overflow: auto; border: none;",
}, },
legend: { legend: {
type: "scroll", type: "scroll",
@ -99,12 +101,6 @@ limitations under the License. -->
bottom: 5, bottom: 5,
containLabel: true, containLabel: true,
}, },
axisPointer: {
label: {
color: "#333",
backgroundColor: "rgba(255, 255, 255, 0.8)",
},
},
xAxis: { xAxis: {
type: "category", type: "category",
axisTick: { axisTick: {

View File

@ -91,19 +91,13 @@ limitations under the License. -->
}); });
const color: string[] = chartColors(keys); const color: string[] = chartColors(keys);
const tooltip = { const tooltip = {
trigger: "none", trigger: "axis",
axisPointer: { textStyle: {
type: "cross", fontSize: 12,
color: "#333", color: "#333",
backgroundColor: "rgba(255, 255, 255, 0.8)",
}, },
// trigger: "axis", enterable: true,
// textStyle: { confine: true,
// fontSize: 12,
// color: "#333",
// },
// enterable: true,
// confine: true,
extraCssText: "max-height: 300px; overflow: auto; border: none;", extraCssText: "max-height: 300px; overflow: auto; border: none;",
}; };
const tips = { const tips = {
@ -133,12 +127,6 @@ limitations under the License. -->
color: props.theme === "dark" ? "#fff" : "#333", color: props.theme === "dark" ? "#fff" : "#333",
}, },
}, },
axisPointer: {
label: {
color: "#333",
backgroundColor: "rgba(255, 255, 255, 0.8)",
},
},
grid: { grid: {
top: showEchartsLegend(keys) ? 35 : 10, top: showEchartsLegend(keys) ? 35 : 10,
left: 0, left: 0,