在vue3+typescript中使用d3 version 7注意的地方

前几天在做一个前端项目,需要在一个vue3+typescript的项目中使用d3

上次做vue2+d3v5的项目已经很多年了,这次不仅是vue3,用的typescript,而且d3也升级到v7了,有很多东西不一样了。

这里记录一下,避免日后忘记。

在使用d3的各个类时,给定type定义

typescript是一个类型严格的语言,在vue3+typescript项目中调用d3时,最好也遵循这一要求,除非万不得已,尽量不要使用any类型。

Selection类

所有的d3的链式操作,都是基于d3.select返回的Selection类进行的,可以说d3最重要的类就是Selection

在d3中,Selection类是这样定义的

/**
 * A D3 Selection of elements.
 *
 * The first generic "GElement" refers to the type of the selected element(s).
 * The second generic "Datum" refers to the type of the datum of a selected element(s).
 * The third generic "PElement" refers to the type of the parent element(s) in the D3 selection.
 * The fourth generic "PDatum" refers to the type of the datum of the parent element(s).
 */
export interface Selection<GElement extends BaseType, Datum, PElement extends BaseType, PDatum>

这是一个typescript模板,模板必须要给定变量类型才能在编译期生成真实的类。这个模板需要给定4各变量类型:

第一个是select要选取的目标的类型,一般来说是DOM上的节点,对于svg而言,主要有:SVGSVGElement、SVGGElement、SVGPathElement这3类。实际上svg还有SVGAElement、SVGCircleElement等很多,但是一般来说d3不用那些类型。不过,如果在应用的时候确实遇到了,也要确实的写上。

按照d3的要求,其实还有一种BaseType是不需要和DOM节点相关的,这个一般来说用于进行数据处理,而不是用于DOM操作。

第二个是数据的类型。实际上在d3内部,如果仅仅是select,此时是没有指定数据类型的,那么这个时候,这个数据类型就是unknown。如果在select后通过链式调用了data或者datum,此时就有了数据类型。

这里给定的数据的类型必须和data或者datum调用的数据的类型一致,否则typescript的严格模式会报错。如果是关闭typescript的strict模式,确实可以关闭掉这个报错,但是我们用typescript不就是为了享受它为我们检查错误吗?所以我从不关闭,发现了错误就去改掉。

第三个是选取的DOM节点的父节点。现实应用中,有时数据是来自父节点的,所以要用到。如果不需要父节点的相关信息,那么可以设定为null。

第四个是选取的DOM节点的父节点的数据类型。如果用到了来自父节点的数据,这里也要匹配。如果没有用到父节点的数据,那么可以设定为undefined。

所以说如果要选取svg根节点,暂时没有指定数据,父节点也不需要,更别提父节点的数据了。那么返回的就是

let svgElement: Ref<SVGSVGElement | null> = ref<SVGSVGElement | null>(null);
let d3svgElement: Selection<SVGSVGElement, unknown, null, undefined>;

这段代码中的第一行是在vue3的单文件组件中,获取DOM节点的方法。第二行则是把DOM节点传递给d3.select后返回的Selection

注意,这里我并没有使用select函数,而是简单的定义了一个变量而已。这是因为在setup阶段,tempelate并没有被编译,DOM并没有生成,所以如果此时用select函数,那么只能返回一个空值

在DOM已经生成的情况下,就可以使用select函数来进行操作了。

type Vector2d = Array<Array<number> >

let nodeSelection: Selection<SVGGElement, unknown, null, undefined> = select<SVGGElement, unknown>(theDOM)
let selectionWithData:Selection<SVGPathElement, Vector2d, null, undefined>=
    nodeSelection.select<SVGPathElement>("path")
    .datum<Vector2d>(pointsXY)

select函数也是一个typescript模板,而且还是一个多态的模板。

/**
     * For each selected element, select the first descendant element that matches the specified selector string.
     * If no element matches the specified selector for the current element, the element at the current index will
     * be null in the returned selection. If multiple elements match the selector, only the first matching element
     * in document order is selected. Selection.select does not affect grouping: it preserves the existing group
     * structure and indexes, and propagates data (if any) to selected children.
     *
     * If the current element has associated data, this data is propagated to the
     * corresponding selected element.
     *
     * The generic represents the type of the descendant element to be selected.
     *
     * @param selector CSS selector string
     */
    // eslint-disable-next-line @definitelytyped/no-unnecessary-generics
    select<DescElement extends BaseType>(selector: string): Selection<DescElement, Datum, PElement, PDatum>;
    /**
     * Create an empty sub-selection. Selection.select does not affect grouping: it preserves the existing group
     * structure and indexes.
     */
    // eslint-disable-next-line @definitelytyped/no-unnecessary-generics
    select<DescElement extends BaseType>(selector: null): Selection<null, undefined, PElement, PDatum>;
    /**
     * For each selected element, select the descendant element returned by the selector function.
     * If no element is returned by the selector function for the current element, the element at the
     * current index will be null in the returned selection. Selection.select does not affect grouping:
     * it preserves the existing group structure and indexes, and propagates data (if any) to selected children.
     *
     * If the current element has associated data, this data is propagated to the
     * corresponding selected element.
     *
     * The generic represents the type of the descendant element to be selected.
     *
     * @param selector A selector function, which is evaluated for each selected element, in order, being passed the current datum (d),
     * the current index (i), and the current group (nodes), with this as the current DOM element (nodes[i]).
     * It must return an element, or null if there is no matching element.
     */
    select<DescElement extends BaseType>(
        selector: ValueFn<GElement, Datum, DescElement>,
    ): Selection<DescElement, Datum, PElement, PDatum>;

当一个Selection进一步的select时,会调用多态的第一个形态,此时,该模板要给定DOM类型,而且该类型必须和实际select的DOM节点类型一致,否则typescript会报错。

而全新创建一个Selection时,会调用多态的第三个形态。里面有2个变量类型,第一个是所选DOM节点的类型,第二个是数据的类型。DOM类型和实际选择的必须一致。而数据类型可以先保留为unknown等待数据绑定操作,unknown类型是兼容的。

这个多态的第二个形态是创建一个空的Selection,没有太大用处。

而在select之后直接链式操作绑定数据的话,数据类型也要给定,如果给定的数据类型和真实绑定的数据类型不一致的话,typescript会报错。

Selection类的call函数

JavaScript中的call是把函数中的this调整为调用call时参数中的第一个实参,并把调用call时后续的参数作为实参传递给函数。

d3的Selectiong类也有一个call函数

    /**
     * Invoke the specified function exactly once, passing in this selection along with any optional arguments.
     * Returns this selection.
     *
     * @param func A function which is passed this selection as the first argument along with any optional arguments.
     * @param args List of optional arguments to be passed to the callback function.
     */
    call<Args extends any[]>(
        func: (selection: Selection<GElement, Datum, PElement, PDatum>, ...args: Args) => void,
        ...args: Args
    ): this;

 call函数的第一个实参是这个被调用的函数,这个函数必须是无返回值的,或者说返回void型

如果这个函数中涉及到this的话,把调用者这个Selection对象作为this传递给这个被call的函数。而且函数中定义的this的类型和Selection的类型必须一致,否则typescript会报错。

call函数的后续实参作为被调用函数的其他实参传入。

Selection对象通过call调用的函数,只执行一次,下次再想执行必须要再次使用call调用。

我一般用这个来处理事件响应,比如点击、拖拽、缩放、平移

ValueFn类

在d3中,很多地方都会允许使用一个函数进行处理。比如说从一组数据里抽出“生日”,然后把“生日”图形呈现出来;或者在select的时候,设定一个条件,满足条件的才选取,不满足条件的忽略;再比如通过attr设定transform属性的内容时,通过一个函数返回这个内容,可以根据数据生成不同的内容以实现更加多变的效果……

这个函数实际上是一个typescript模板

/**
 * Callback type for selections and transitions
 */
export type ValueFn<T extends BaseType, Datum, Result> = (
    this: T,
    datum: Datum,
    index: number,
    groups: T[] | ArrayLike<T>,
) => Result;

这个模板输入3种类型,并返回一个函数。

输入的三种类型中:

第一种输入类型Basetype和Selection中的Basetype差不多,代表着这个函数作用节点的类型,如果节点类型不匹配,typescript会报错。

第二种输入类型Datum是数据类型。如果数据类型和真实的类型不匹配,typescript也会报错。

第三种输入类型其实是“生成出来的这个函数”的返回值类型,必须要和调用这个函数的位置的需求相匹配。

而返回的这个函数,从模板上看有4个形参和1个返回值。但是4个形参中的第一个是this,实际上也就是调用这个函数的对象,一般来说是Selectiong中的DOM节点。

一般来说ValueFn是只读的,就是说不会修改什么数据,但是理论上确实可以做很多事情,比如说对调用者本身做一些修改。即使对调用者本身不做任何修改,有些时候确实需要调用者的一些信息。

而这三个形参中:

第一个形参是输入的数据

第二个形参是调用者调用时的索引

第三个形参是调用者的数组。

在d3中,在select后通过链式调用data绑定数据后,d3会分析输入数据的数量,并根据这个数量在enter/exit/remove/join函数中调整Selection子节点的数量。比如说输入数据有5条,但是Selection内只有3个path子节点,而链式操作有需要有和输入数据一一对应的path子节点,那么就会通过enter/exit/remove/join自动调整,把path节点增加到5个。这5个path子节点所组成的Array就是这第三个形参。

因为箭头函数的形参中不能包含this,所以有时候我们需要用下面这种方法来定义ValueFn,然后在用到的地方调用

let SymbolTransform: ValueFn<SVGPathElement | BaseType, Vector1d, string> = function (
    this: SVGPathElement | BaseType,
    d: Vector1d,
    i: number,
    a: Array<BaseType | SVGPathElement> | ArrayLike<BaseType | SVGPathElement>
): string {
    const x = xScale(d[2]);
    const y = yScale(d[0]);
    return `translate(${x},${y})`;
};

注意,在d3v5之前,这个函数的第4个形参不是调用者的Array,而是数据d的Array。所以如果遇到了代码迁移之类的工作,在这里要调整。

事件响应类

D3 6.0 migration guide / D3 | Observable

d3在从v5升级到v6时有很多改变,其中一个重要变化就是事件管理器大幅更新了。

事件响应函数

在d3的Selection类中,有一个on函数,可以通过这个函数对事件绑定一个函数,以此实现事件响应。这个on函数的定义是这样的:

    /**
     * Return the currently-assigned listener for the specified event typename on the first (non-null) selected element,
     * if any, If multiple typenames are specified, the first matching listener is returned.
     *
     * @param typenames The typenames is a string event type, such as click, mouseover, or submit; any DOM event type supported by your browser may be used.
     * The type may be optionally followed by a period (.) and a name; the optional name allows multiple callbacks to be registered
     * to receive events of the same type, such as click.foo and click.bar. To specify multiple typenames, separate typenames with spaces,
     * such as "input change"" or "click.foo click.bar".
     */
    on(typenames: string): ((this: GElement, event: any, d: Datum) => void) | undefined;
    /**
     * Remove a listener for the specified event type names. To remove all listeners for a given name,
     * pass null as the listener and ".foo" as the typename, where foo is the name; to remove all listeners with no name, specify "." as the typename.
     *
     * @param typenames The typenames is a string event type, such as click, mouseover, or submit; any DOM event type supported by your browser may be used.
     * The type may be optionally followed by a period (.) and a name; the optional name allows multiple callbacks to be registered
     * to receive events of the same type, such as click.foo and click.bar. To specify multiple typenames, separate typenames with spaces,
     * such as "input change"" or "click.foo click.bar".
     * @param listener null to indicate removal of listener
     */
    on(typenames: string, listener: null): this;
    /**
     * Add an event listener for the specified event type names. If an event listener was previously registered for the same typename
     * on a selected element, the old listener is removed before the new listener is added.
     *
     * When a specified event is dispatched on a selected node, the specified listener will be evaluated for each selected element.
     *
     * @param typenames The typenames is a string event type, such as click, mouseover, or submit; any DOM event type supported by your browser may be used.
     * The type may be optionally followed by a period (.) and a name; the optional name allows multiple callbacks to be registered
     * to receive events of the same type, such as click.foo and click.bar. To specify multiple typenames, separate typenames with spaces,
     * such as "input change"" or "click.foo click.bar".
     * @param listener A listener function which will be evaluated for each selected element,
     * being passed the current event (event) and the current datum (d), with this as the current DOM element (event.currentTarget).
     * Listeners always see the latest datum for their element.
     * Note: while you can use event.pageX and event.pageY directly,
     * it is often convenient to transform the event position to the local coordinate system of that element that received the event using d3.pointer.
     * @param options An optional options object may specify characteristics about the event listener, such as wehether it is captures or passive; see element.addEventListener.
     */
    on(typenames: string, listener: (this: GElement, event: any, d: Datum) => void, options?: any): this;

这是一个typescript的多态函数,很明显,第二种多态——不绑定任何相应函数——是最简单的,也是默认的。而第一种是最常用的,也就是绑定一个“有三个形参的函数”,而这个函数:

第一个形参是调用者this,也就是被绑定的Selection对象——也就是DOM节点。

第二个形参是事件,注意这个事件的类型是any,但是实际上必须是d3的event类型的标签字符串。而d3的event一共分为4个大类brush/dispatch/drag/zoom,每个大类还有多个event类型。

第三个形参是数据,就是Selection中绑定的数据,其类型也必须一致,否则typescript会报错。

通过事件响应函数,可以对第三个形参所对应的实参进行操作,比如在drag事件响应函数中,把数据修改为drag事件的x、y坐标。

事件响应函数没有像ValueFn一样有一个单独定义的模板,所以我们只好手动定义。因为这个函数包含了this,所以定义起来稍微有点麻烦,只能使用变量式函数定义法。

let ClickSymbol: (this: SVGPathElement, event: any, d: Vector1d) => void = function (
    event: MouseEvent,
    d: Vector1d
): void {
    console.log(this)
};

node.select<SVGGElement>(".symbolElement")
    .selectAll<SVGPathElement, Vector1d>("path")
    .data<Vector1d>(pointsXY)
    .join("path")
    .attr("d", symbol<Vector1d>(symbolCircle, 10))
    .attr("pointnum", (d: Vector1d, i: number, a: Array<SVGPathElement> | ArrayLike<SVGPathElement>) => i)
    .style("stroke", "black")
    .style("fill", "white")
    .attr("transform", SymbolTransform)
    .on("click", ClickSymbol);

调用的时候用Selection的链式操作,通过on函数调用。

注意pointnum这里调用了一个ValueFn,只不过它是匿名函数定义的,而匿名函数中不可以在形参里用this,所以这里只有3个形参。

Zoom函数

zoom这个词的愿意虽然是缩放,但是d3的zoom却还可以平移。

Zoomable Scatterplot / D3 | Observable

这里有一个典型的zoom应用的例子,虽然是用JavaScript写的,但是其基本思路和typescript并无不同,如果要迁移到typescript,直接加上类型标注基本就可以。

ZoomBehavior是一个模板函数,在Javascript中,函数也是一个对象,可以有自己的属性,函数在运行时也可以调用自己的属性以实现更加复杂多变的功能。模板的两个变量类型,其中第一个是该函数要作用的DOM节点的类型,在typescript内置对象中定一个了一个Element类型,几乎涵盖了所有HTML的DOM类型;第二个是数据类型。也就是对应着Selection对象中DOM节点的类型和绑定的数据的类型。

这个函数是由zoom函数生成的,然后通过链式调用可以修改其内部参数。其中最主要的就是通过on函数增加对zoom事件的响应

let xScale:ScaleLinear<number, number, never> = scaleLinear();
let yScale:ScaleLinear<number, number, never> = scaleLinear();

const zoomBeh: ZoomBehavior<Element, unknown> = zoom()
    .scaleExtent([0.5, 32])
    .on("zoom", (event: D3ZoomEvent<Element, any>, d: unknown): void => {
        gElement.attr("transform", event.transform.toString());
        gElement.selectAll("rect").attr("stroke-width", 2 / event.transform.k);
        event.transform.rescaleX(xScale);
        event.transform.rescaleY(yScale);
    });

select(plotElement.value as Element)
    .call(zoomBeh)
    .call(zoomBeh.transform, zoomIdentity);

比如说gElement是一个Selection对象,里面包含有一些图形。而我们需要它对zoom事件有所响应,直接通过

gElement.attr("transform", event.transform.toString());

 就可以让它随zoom事件平移和缩放。

而如果说这里面包含的图案中有一些是线条图案,而我们不希望线条的宽度随着缩放而有所变化,也就是说只缩放尺寸,不缩放粗细。就可以这样

gElement.selectAll("rect").attr("stroke-width", 2 / event.transform.k);

这个例子当中的线条图案是rect,如果是其他的图案,比如circle/path之类的,方法类似。

而比例尺也要有针对性的进行调整

event.transform.rescaleX(xScale);
event.transform.rescaleY(yScale);

另外,注意on函数的调用。

就如“事件响应函数”章节所述,on函数的第一个参数是事件类型,是一个字符串,而第二个参数是事件的响应函数。

这个响应函数包含3个形参,但是第一个形参是this,而这里是采用匿名函数的方法直接定义的,所以第一个this就不要写在形参表中,形参表中只有event和d。

而由于on的第一个参数是字符串zoom,所以事件类型也是确定的,这里就是D3ZoomEvent<Element, any>,这里指定事件类型有助于响应函数的编码,因为D3ZoomEvent类有transform成员以及配套的其他函数,编码时编辑器可以高亮提示、自动跳转,typescript也会做代码检查。

最后通过

select(plotElement.value as Element)
    .call(zoomBeh)
    .call(zoomBeh.transform, zoomIdentity);

把zoom函数绑定到对应的DOM元素上,可以看到这个DOM元素是vue3直接操作DOM元素,一旦这个DOM元素上发生了zoom缩放事件,就会响应。

zoomIdentify是d3自带的一个类。 第二个call是对平移进行响应。

Drag函数

Drag函数是一个模板

/**
 * A D3 Drag Behavior
 *
 * The first generic refers to the type of element to be dragged.
 * The second generic refers to the type of the datum of the dragged element.
 * The third generic refers to the type of the drag behavior subject.
 *
 * The subject of a drag gesture represents the thing being dragged.
 * It is computed when an initiating input event is received,
 * such as a mousedown or touchstart, immediately before the drag gesture starts.
 * The subject is then exposed as event.subject on subsequent drag events for this gesture.
 *
 * The default subject is the datum of the element in the originating selection (see drag)
 * that received the initiating input event; if this datum is undefined,
 * an object representing the coordinates of the pointer is created.
 * When dragging circle elements in SVG, the default subject is thus the datum of the circle being dragged.
 * With Canvas, the default subject is the canvas element’s datum (regardless of where on the canvas you click).
 * In this case, a custom subject accessor would be more appropriate,
 * such as one that picks the closest circle to the mouse within a given search radius.
 */
export interface DragBehavior<GElement extends DraggedElementBaseType, Datum, Subject> extends Function

和zoom函数不同,drag函数的模板有3个变量类型,其中第一个和第二个和zoom的模板一样代表着调用者的类型和绑定的数据的类型。第三个是drag事件的具体类型。这是因为drag事件包含3个具体类型:start/drag/end,而zoom是单纯的事件类型。

DragBehavior函数通过drag函数生成,并通过链式调用可以修改其内部参数。并最终通过call函数绑定到Selection实例上。

let symbolDragBeh: DragBehavior<SVGPathElement, Vector1d, SubjectPosition | Vector1d>;
symbolDragBeh = drag<SVGPathElement, Vector1d>()
    .on("start", SymbolDragStart)
    .on("drag", SymbolDragMove)
    .on("end", SymbolDragEnd);

node.select<SVGGElement>(".symbolElement")
    .selectAll<SVGPathElement, Vector1d>("path")
    .data<Vector1d>(pointsXY)
    .join("path")
    .attr("d", symbol<Vector1d>(symbolCircle, 10))
    .attr("pointnum", (d: Vector1d, i: number, a: Array<SVGPathElement> | ArrayLike<SVGPathElement>) => i)
    .style("stroke", "black")
    .style("fill", "white")
    .attr("transform", SymbolTransform)
    .on("click", ClickSymbol);
    .call(symbolDragBeh)

注意,这里click和drag是两个完全不同的事件,要分别绑定,而且绑定的方法不一样。因为drag事件非常复杂,要分为start/drag/end这三个步骤。

而这3个步骤所对应的响应函数则是这样的:

let SymbolDragStart: (this: SVGPathElement, event: MouseEvent, d: Vector1d) => void = function (event, d) {
    emit('DragChangeCurrentPoint', Number.parseInt(d.getAttribute('pointnum')))
};

let SymbolDragMove: (this: SVGPathElement, event: MouseEvent, d: Vector1d) => void = function (event, d) {
    emit('DragEditPoint', [event.x, event.y])
};
let SymbolDragEnd: (this: SVGPathElement, event: MouseEvent, d: Vector1d) => void = function (event, d) {
    emit('DragEnd')
};

可以看到,这就是3个常规的事件响应函数,只要按照正常的事件响应函数编写即可。

vue3和d3的集成

直接在template中定义svg的DOM树

首先,最好是在vue3的单文件组件的template中直接定义好DOM树,尤其是svg的树。

市面上很多代码非常乐于利用ts/js在代码中生成DOM树节点,这也是前端进入框架时代以后的绝大多数风格。

这样不是说不行,但是代码运行效率会低一些,尤其是在使用d3的时候会比较敏感。这倒不是说d3会使得这样的代码执行效率更慢,而是因为d3是一个高效的库,一般来讲,用到d3的项目对于运行速度往往比其他项目要求更苛刻。

在vue3大型项目中,可以在单文件组件的template段直接把svg的基本DOM树定义好,然后使用ts来直接操作这些DOM节点,这样会快一些。

在vue3中d3怎样select

模板引用 | Vue.js

根据官方文档,通过ref函数可以获取template中定义好的DOM节点。然后把这个节点直接传给d3的select函数即可。要注意的点是:

1) 因为ref是响应式的,要通过.value属性来获取其本来值。

2) 从语法上讲,ref有可能是null,所以要通过后缀!明确告诉typescript这个值非空

生命周期

根据vue3的官方文档

https://cn.vuejs.org/guide/essentials/lifecycle.html

在beforeMount和mounted这两个钩子中间,DOM被编译并渲染。如果是在template中定义svg的DOM树,那么到了mounted这个钩子的时候,DOM才真实存在;在此之前,不存在。所以如果在不存在的时候select,那就只能返回空的选择集。

所以所有的Selection对象实例都可以在setup阶段定义,但是具体的赋值却必须要等到onMounted()中进行才可以。否则一旦select为空,则后续所有操作都是虚无,最后在渲染完成后,虽然代码执行看起来毫无错误,但是页面上却什么也没有。

当然,如果不在template中定义svg的DOM树,而是通过d3的create/append函数在setup阶段创建svg相关节点,那可以忽略生命周期的影响,不过速度会略微慢一点。

<template>
    <svg xmlns="http://www.w3.org/2000/svg" ref="svgElement"></svg>
</template>
import { ref, Ref, onMounted } from "vue";
import { select, Selection } from "d3";

let svgElement: Ref<SVGSVGElement | null> = ref<SVGSVGElement | null>(null);
let d3svgElement: Selection<SVGSVGElement, unknown, null, undefined>;

onMounted(():void=>{
    d3svgElement = select(svgElement.value!)
})

在上一篇帖子中

https://blog.csdn.net/silent_missile/article/details/138819959

我用vite创建了一个新的vue3项目,然后把d3代码嵌入其中,并使用了composite api的setup,所以setup中所有的select都没有选中,测试显示代码运行没有任何错误,但是图形就是显示不出来。这是因为如果select目标为空,d3是不会报错的。

然后我用了vite创建了一个新的纯typescript项目,并把DOM用硬编码写在HTML里,就能正确显示。

然后我不再使用template硬编码svg的DOM树,而是采用d3的create/append创建DOM节点作为d3的Selection,这样也没问题。

最终我确认这是vue3的生命周期的问题,并把所有的select移动到onMounted里,这才算解决了这个问题。

  • 17
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Vue3+TypeScript 使用 OpenLayers 显示不同地物类别的图例,可以通过以下步骤实现: 1. 安装 OpenLayers 和 @types/ol 库: ```bash npm install ol @types/ol ``` 2. 在 Vue3 组件引入 OpenLayers 库: ```javascript import Map from 'ol/Map'; import View from 'ol/View'; import TileLayer from 'ol/layer/Tile'; import OSM from 'ol/source/OSM'; import VectorLayer from 'ol/layer/Vector'; import VectorSource from 'ol/source/Vector'; import Style from 'ol/style/Style'; import Fill from 'ol/style/Fill'; import Stroke from 'ol/style/Stroke'; import Circle from 'ol/style/Circle'; ``` 3. 在 Vue3 组件定义地图容器和图例容器: ```html <template> <div> <div id="map" style="width: 100%; height: 400px;"></div> <div id="legend"></div> </div> </template> ``` 4. 在 Vue3 组件使用 OpenLayers 创建地图和图层: ```javascript <script lang="ts"> import { defineComponent } from 'vue'; import Map from 'ol/Map'; import View from 'ol/View'; import TileLayer from 'ol/layer/Tile'; import OSM from 'ol/source/OSM'; import VectorLayer from 'ol/layer/Vector'; import VectorSource from 'ol/source/Vector'; import Style from 'ol/style/Style'; import Fill from 'ol/style/Fill'; import Stroke from 'ol/style/Stroke'; import Circle from 'ol/style/Circle'; export default defineComponent({ name: 'MapComponent', mounted() { const map = new Map({ target: 'map', layers: [ new TileLayer({ source: new OSM(), }), new VectorLayer({ source: new VectorSource({ features: [ // 添加地物要素 ], }), style: (feature) => { // 根据不同的地物类别设置样式 }, }), ], view: new View({ center: [0, 0], zoom: 2, }), }); // 创建图例 const legend = document.getElementById('legend'); legend.innerHTML = ` <div> <span style="background-color: #ff0000;"></span> <span>红色代表...</span> </div> <div> <span style="background-color: #00ff00;"></span> <span>绿色代表...</span> </div> <div> <span style="background-color: #0000ff;"></span> <span>蓝色代表...</span> </div> `; }, }); </script> ``` 在上面的代码,我们可以根据不同的地物类别来设置样式,例如:红色代表建筑物,绿色代表森林,蓝色代表水域等等。我们还创建了一个图例容器,并添加了不同地物类别的说明和颜色示例。 以上就是使用 OpenLayers 在 Vue3+TypeScript 显示不同地物类别的图例的方法。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值