mirror of
https://github.com/ecomfe/vue-echarts.git
synced 2025-10-29 07:56:12 +08:00
294 lines
7.1 KiB
TypeScript
294 lines
7.1 KiB
TypeScript
/* eslint-disable vue/multi-word-component-names */
|
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
import {
|
|
defineComponent,
|
|
unref,
|
|
shallowRef,
|
|
toRefs,
|
|
watch,
|
|
computed,
|
|
inject,
|
|
onMounted,
|
|
onUnmounted,
|
|
h,
|
|
nextTick,
|
|
watchEffect,
|
|
getCurrentInstance,
|
|
Vue2,
|
|
type PropType,
|
|
type InjectionKey
|
|
} from "vue-demi";
|
|
import { init as initChart } from "echarts/core";
|
|
import type {
|
|
EChartsType,
|
|
EventTarget,
|
|
Option,
|
|
Theme,
|
|
ThemeInjection,
|
|
InitOptions,
|
|
InitOptionsInjection,
|
|
UpdateOptions,
|
|
UpdateOptionsInjection
|
|
} from "./types";
|
|
import {
|
|
usePublicAPI,
|
|
useAutoresize,
|
|
autoresizeProps,
|
|
useLoading,
|
|
loadingProps
|
|
} from "./composables";
|
|
import { omitOn } from "./utils";
|
|
import "./style.css";
|
|
|
|
const TAG_NAME = "x-vue-echarts";
|
|
|
|
if (Vue2) {
|
|
Vue2.config.ignoredElements.push(TAG_NAME);
|
|
}
|
|
|
|
export const THEME_KEY = "ecTheme" as unknown as InjectionKey<ThemeInjection>;
|
|
export const INIT_OPTIONS_KEY =
|
|
"ecInitOptions" as unknown as InjectionKey<InitOptionsInjection>;
|
|
export const UPDATE_OPTIONS_KEY =
|
|
"ecUpdateOptions" as unknown as InjectionKey<UpdateOptionsInjection>;
|
|
export { LOADING_OPTIONS_KEY } from "./composables";
|
|
|
|
export default defineComponent({
|
|
name: "echarts",
|
|
props: {
|
|
option: Object as PropType<Option>,
|
|
theme: {
|
|
type: [Object, String] as PropType<Theme>
|
|
},
|
|
initOptions: Object as PropType<InitOptions>,
|
|
updateOptions: Object as PropType<UpdateOptions>,
|
|
group: String,
|
|
manualUpdate: Boolean,
|
|
...autoresizeProps,
|
|
...loadingProps
|
|
},
|
|
inheritAttrs: false,
|
|
setup(props, { attrs }) {
|
|
const root = shallowRef<HTMLElement>();
|
|
const chart = shallowRef<EChartsType>();
|
|
const manualOption = shallowRef<Option>();
|
|
const defaultTheme = inject(THEME_KEY, null);
|
|
const defaultInitOptions = inject(INIT_OPTIONS_KEY, null);
|
|
const defaultUpdateOptions = inject(UPDATE_OPTIONS_KEY, null);
|
|
|
|
const { autoresize, manualUpdate, loading, loadingOptions } = toRefs(props);
|
|
|
|
const realOption = computed(
|
|
() => manualOption.value || props.option || null
|
|
);
|
|
const realTheme = computed(() => props.theme || unref(defaultTheme) || {});
|
|
const realInitOptions = computed(
|
|
() => props.initOptions || unref(defaultInitOptions) || {}
|
|
);
|
|
const realUpdateOptions = computed(
|
|
() => props.updateOptions || unref(defaultUpdateOptions) || {}
|
|
);
|
|
const nonEventAttrs = computed(() => omitOn(attrs));
|
|
|
|
// @ts-expect-error listeners for Vue 2 compatibility
|
|
const listeners = getCurrentInstance().proxy.$listeners;
|
|
|
|
function init(option?: Option) {
|
|
if (!root.value) {
|
|
return;
|
|
}
|
|
|
|
const instance = (chart.value = initChart(
|
|
root.value,
|
|
realTheme.value,
|
|
realInitOptions.value
|
|
));
|
|
|
|
if (props.group) {
|
|
instance.group = props.group;
|
|
}
|
|
|
|
let realListeners = listeners;
|
|
if (!realListeners) {
|
|
realListeners = {};
|
|
|
|
Object.keys(attrs)
|
|
.filter(key => key.indexOf("on") === 0 && key.length > 2)
|
|
.forEach(key => {
|
|
// onClick -> c + lick
|
|
// onZr:click -> z + r:click
|
|
let event = key.charAt(2).toLowerCase() + key.slice(3);
|
|
|
|
// clickOnce -> ~click
|
|
// zr:clickOnce -> ~zr:click
|
|
if (event.substring(event.length - 4) === "Once") {
|
|
event = `~${event.substring(0, event.length - 4)}`;
|
|
}
|
|
|
|
realListeners[event] = attrs[key];
|
|
});
|
|
}
|
|
|
|
Object.keys(realListeners).forEach(key => {
|
|
let handler = realListeners[key];
|
|
|
|
if (!handler) {
|
|
return;
|
|
}
|
|
|
|
let event = key.toLowerCase();
|
|
if (event.charAt(0) === "~") {
|
|
event = event.substring(1);
|
|
handler.__once__ = true;
|
|
}
|
|
|
|
let target: EventTarget = instance;
|
|
if (event.indexOf("zr:") === 0) {
|
|
target = instance.getZr();
|
|
event = event.substring(3);
|
|
}
|
|
|
|
if (handler.__once__) {
|
|
delete handler.__once__;
|
|
|
|
const raw = handler;
|
|
|
|
handler = (...args: any[]) => {
|
|
raw(...args);
|
|
target.off(event, handler);
|
|
};
|
|
}
|
|
|
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
// @ts-ignore EChartsType["on"] is not compatible with ZRenderType["on"]
|
|
// but it's okay here
|
|
target.on(event, handler);
|
|
});
|
|
|
|
function resize() {
|
|
if (instance && !instance.isDisposed()) {
|
|
instance.resize();
|
|
}
|
|
}
|
|
|
|
function commit() {
|
|
const opt = option || realOption.value;
|
|
if (opt) {
|
|
instance.setOption(opt, realUpdateOptions.value);
|
|
}
|
|
}
|
|
|
|
if (autoresize.value) {
|
|
// Try to make chart fit to container in case container size
|
|
// is changed synchronously or in already queued microtasks
|
|
nextTick(() => {
|
|
resize();
|
|
commit();
|
|
});
|
|
} else {
|
|
commit();
|
|
}
|
|
}
|
|
|
|
function setOption(option: Option, updateOptions?: UpdateOptions) {
|
|
if (props.manualUpdate) {
|
|
manualOption.value = option;
|
|
}
|
|
|
|
if (!chart.value) {
|
|
init(option);
|
|
} else {
|
|
chart.value.setOption(option, updateOptions || {});
|
|
}
|
|
}
|
|
|
|
function cleanup() {
|
|
if (chart.value) {
|
|
chart.value.dispose();
|
|
chart.value = undefined;
|
|
}
|
|
}
|
|
|
|
let unwatchOption: (() => void) | null = null;
|
|
watch(
|
|
manualUpdate,
|
|
manualUpdate => {
|
|
if (typeof unwatchOption === "function") {
|
|
unwatchOption();
|
|
unwatchOption = null;
|
|
}
|
|
|
|
if (!manualUpdate) {
|
|
unwatchOption = watch(
|
|
() => props.option,
|
|
(option, oldOption) => {
|
|
if (!option) {
|
|
return;
|
|
}
|
|
if (!chart.value) {
|
|
init();
|
|
} else {
|
|
chart.value.setOption(option, {
|
|
notMerge: option.value !== oldOption?.value,
|
|
...realUpdateOptions.value
|
|
});
|
|
}
|
|
},
|
|
{ deep: true }
|
|
);
|
|
}
|
|
},
|
|
{
|
|
immediate: true
|
|
}
|
|
);
|
|
|
|
watch(
|
|
[realTheme, realInitOptions],
|
|
() => {
|
|
cleanup();
|
|
init();
|
|
},
|
|
{
|
|
deep: true
|
|
}
|
|
);
|
|
|
|
watchEffect(() => {
|
|
if (props.group && chart.value) {
|
|
chart.value.group = props.group;
|
|
}
|
|
});
|
|
|
|
const publicApi = usePublicAPI(chart);
|
|
|
|
useLoading(chart, loading, loadingOptions);
|
|
|
|
useAutoresize(chart, autoresize, root);
|
|
|
|
onMounted(() => {
|
|
init();
|
|
});
|
|
|
|
onUnmounted(cleanup);
|
|
|
|
return {
|
|
chart,
|
|
root,
|
|
setOption,
|
|
nonEventAttrs,
|
|
...publicApi
|
|
};
|
|
},
|
|
render() {
|
|
// Vue 3 and Vue 2 have different vnode props format:
|
|
// See https://v3-migration.vuejs.org/breaking-changes/render-function-api.html#vnode-props-format
|
|
const attrs = (
|
|
Vue2 ? { attrs: this.nonEventAttrs } : { ...this.nonEventAttrs }
|
|
) as any;
|
|
attrs.ref = "root";
|
|
attrs.class = attrs.class ? ["echarts"].concat(attrs.class) : "echarts";
|
|
return h(TAG_NAME, attrs);
|
|
}
|
|
});
|