deck-overlay 中
首先使用 d3 中的 scaleQuantile 将数据进行分类, scaleQuantile 方法是 d3 中的一种数据分类方法 (https://www.cnblogs.com/kidsitcn/p/7182274.html)
https://raw.githubusercontent.com/uber-common/deck.gl-data/master/examples/arc/counties.json
- _getArcs({data, selectedFeature}) {
- if (!data || !selectedFeature) {
- return null;
- }
- const {flows, centroid} = selectedFeature.properties;
- const arcs = Object.keys(flows).map(toId => {
- const f = data[toId];
- return {
- source: centroid,
- target: f.properties.centroid,
- value: flows[toId]
- };
- });
- const scale = scaleQuantile()
- .domain(arcs.map(a => Math.abs(a.value)))
- .range(inFlowColors.map((c, i) => i));
- arcs.forEach(a => {
- a.gain = Math.sign(a.value);
- a.quantile = scale(Math.abs(a.value));
- });
- return arcs;
- }
scaleQuantile 是一种将连续的值转化成离散的方法, 最终离散成这几种颜色分类
arc-layer 中
这里还是使用了实例化的方法, 先添加一堆实例化变量:
- initializeState() {
- const attributeManager = this.getAttributeManager();
- /* eslint-disable max-len */
- attributeManager.addInstanced({
- instancePositions: {
- size: 4,
- transition: true,
- accessor: ['getSourcePosition', 'getTargetPosition'],
- update: this.calculateInstancePositions
- },
- instanceSourceColors: {
- size: 4,
- type: GL.UNSIGNED_BYTE,
- transition: true,
- accessor: 'getSourceColor',
- update: this.calculateInstanceSourceColors
- },
- instanceTargetColors: {
- size: 4,
- type: GL.UNSIGNED_BYTE,
- transition: true,
- accessor: 'getTargetColor',
- update: this.calculateInstanceTargetColors
- }
- });
- /* eslint-enable max-len */
- }
然后是制作图形, 这里使用 50 个点来模拟一条抛物线的效果
- _getModel(gl) {
- let positions = [];
- const NUM_SEGMENTS = 50; // 利用 50 个点来模拟曲线
- /*
- * (0, -1)-------------_(1, -1)
- * | _,-" |
- * o _,-" o
- * | _,-" |
- * (0, 1)"-------------(1, 1)
- */
- for (let i = 0; i <NUM_SEGMENTS; i++) { // 使用三角带的方式来绘制三角形, 同时这里的 - 1 和 1 也是为了在绘制宽度的时候确定法向量的偏移
- positions = positions.concat([i, -1, 0, i, 1, 0]);
- }
- const model = new Model(
- gl,
- Object.assign({}, this.getShaders(), {
- id: this.props.id,
- geometry: new Geometry({
- drawMode: GL.TRIANGLE_STRIP,
- attributes: {
- positions: new Float32Array(positions)
- }
- }),
- isInstanced: true,
- shaderCache: this.context.shaderCache // 缓存着色器, 我怀疑自己写的 hexagon 偏慢也跟这个有关系
- })// 绘制物体, 这里是 5.x 的版本在新的版本中还要设定 instanceCount 参数, 来控制绘制实例的数量
- );
- model.setUniforms({numSegments: NUM_SEGMENTS});
- return model;
- }
下面是计算一些实例变量, 根据 data 的数量来控制, 但是 luma 好像会默认给实例变量的数组分配大小, 实际的 value 中有一些多余的空间, 如果数据量小的话, 可能绘制不出来; 比如: data 有 22 条线, 按照如下计算, instancePositions 可用的 value 就只有 88 个元素.
- calculateInstancePositions(attribute) {
- const {data, getSourcePosition, getTargetPosition} = this.props;
- const {value, size} = attribute;
- let i = 0;
- for (const object of data) {
- const sourcePosition = getSourcePosition(object);
- const targetPosition = getTargetPosition(object);
- value[i + 0] = sourcePosition[0];
- value[i + 1] = sourcePosition[1];
- value[i + 2] = targetPosition[0];
- value[i + 3] = targetPosition[1];
- i += size;
- }
- }
- calculateInstancePositions64Low(attribute) {
- const {data, getSourcePosition, getTargetPosition} = this.props;
- const {value, size} = attribute;
- let i = 0;
- for (const object of data) {
- const sourcePosition = getSourcePosition(object);
- const targetPosition = getTargetPosition(object);
- value[i + 0] = fp64LowPart(sourcePosition[0]);
- value[i + 1] = fp64LowPart(sourcePosition[1]);
- value[i + 2] = fp64LowPart(targetPosition[0]);
- value[i + 3] = fp64LowPart(targetPosition[1]);
- i += size;
- }
- }
- calculateInstanceSourceColors(attribute) {
- const {data, getSourceColor} = this.props;
- const {value, size} = attribute;
- let i = 0;
- for (const object of data) {
- const color = getSourceColor(object);
- value[i + 0] = color[0];
- value[i + 1] = color[1];
- value[i + 2] = color[2];
- value[i + 3] = isNaN(color[3]) ? 255 : color[3];
- i += size;
- }
- }
- calculateInstanceTargetColors(attribute) {
- const {data, getTargetColor} = this.props;
- const {value, size} = attribute;
- let i = 0;
- for (const object of data) {
- const color = getTargetColor(object);
- value[i + 0] = color[0];
- value[i + 1] = color[1];
- value[i + 2] = color[2];
- value[i + 3] = isNaN(color[3]) ? 255 : color[3];
- i += size;
- }
- }
着色器代码
- #define SHADER_NAME arc-layer-vertex-shader
- attribute vec3 positions; // 几何图形的坐标, 同时这里面也编码了一些信息, x 代表线段索引, y 可以代表偏移方向
- // 本次可用的一些实例变量
- attribute vec4 instanceSourceColors;// 起点的颜色
- attribute vec4 instanceTargetColors; // 终点的颜色
- attribute vec4 instancePositions; // 前两个值记录了起点经纬度, 后两个值记录了终点经纬度
- attribute vec3 instancePickingColors;
- uniform float numSegments; // 抛物线的线段数量
- uniform float strokeWidth; // 线宽度
- uniform float opacity;
- varying vec4 vColor;
- // source 和 target 是在 3d 空间中的单位, ratio 代表本此线段在总线段数目的比值范围在 0~1, 返回值时抛物线高度的平方
- // 这里的方式决定高度单位与 source/target 的单位保持一致
- float paraboloid(vec2 source, vec2 target, float ratio) {
- vec2 x = mix(source, target, ratio); // 获取该线段节点对应的直线位置
- vec2 center = mix(source, target, 0.5);// 取中心点, 充分利用 glsl 内建函数, 提升性能
- // 抛物线的公式应该是 y * y = (source - center)^2 - (x - center)^2;
- float dSourceCenter = distance(source, center);
- float dXCenter = distance(x, center);
- return (dSourceCenter + dXCenter) * (dSourceCenter - dXCenter);
- }
- // 在屏幕空间中计算偏移值, 最后在反算到裁切空间, 也就是 ndc 空间
- // offset_direction 在 position 的 y 坐标中记录
- // offset vector by strokeWidth pixels
- // offset_direction is -1 (left) or 1 (right)
- vec2 getExtrusionOffset(vec2 line_clipspace, float offset_direction) {
- // normalized direction of the line
- // ndc 空间中的坐标乘以屏幕宽高像素, 转换成 2 维屏幕像素; 然后归一化成单位向量
- vec2 dir_screenspace = normalize(line_clipspace * project_uViewportSize);
- // rotate by 90 degrees
- dir_screenspace = vec2(-dir_screenspace.y, dir_screenspace.x); // 求法线向量
- // 法向量乘以偏移方向乘以宽度一半获取在屏幕空间中的偏移值
- vec2 offset_screenspace = dir_screenspace * offset_direction * strokeWidth / 2.0;
- // 将屏幕坐标反算到 ndc 空间
- vec2 offset_clipspace = project_pixel_to_clipspace(offset_screenspace).xy;
- return offset_clipspace; // 返回 ndc 空间的偏移量
- }
- float getSegmentRatio(float index) { // 返回线段索引在总线段数目中的比值, 转换成 0~1 之间
- return smoothstep(0.0, 1.0, index / (numSegments - 1.0));
- }
- vec3 getPos(vec2 source, vec2 target, float segmentRatio) { // 获取线段节点在三维空间中的位置
- float vertex_height = paraboloid(source, target, segmentRatio); // 获取高度信息
- return vec3(
- mix(source, target, segmentRatio), // 获取节点的 x/y 坐标
- sqrt(max(0.0, vertex_height))// 获取节点的高度坐标
- );
- }
- void main(void) {
- // 将 insance 中编码的起终点的经纬度分别转换成瓦片像素单位
- vec2 source = project_position(instancePositions.xy);
- vec2 target = project_position(instancePositions.zw);
- float segmentIndex = positions.x;// 节点的线段索引
- float segmentRatio = getSegmentRatio(segmentIndex);
- // if it's the first point, use next - current as direction
- // otherwise use current - prev
- // 这里处理方式比较巧妙, 充分利用内建函数优势;
- // step(edge, x) 作用如: x>=edge ? 1.0 : 0.0
- // 所以上面英文注释所说, 如果是起点就使用 next-curr, 其他的都是用 curr - prev
- //float indexDir = mix(-1.0, 1.0, step(segmentIndex, 0.0));
- float indexDir = mix(-1.0, 1.0, (segmentIndex <= 0.0 ? 1.0 : 0.0));
- // 根据 indexDir 获取下一段或者上一个线段节点的比值
- float nextSegmentRatio = getSegmentRatio(segmentIndex + indexDir);
- // 获取两个节点的 3 维世界坐标并转化成 ndc 坐标
- vec3 currPos = getPos(source, target, segmentRatio);
- vec3 nextPos = getPos(source, target, nextSegmentRatio);
- vec4 curr = project_to_clipspace(vec4(currPos, 1.0));
- vec4 next = project_to_clipspace(vec4(nextPos, 1.0));
- // extrude
- // 进行线宽拉伸, 获取法线方向的偏移
- vec2 offset = getExtrusionOffset((next.xy - curr.xy) * indexDir, positions.y);
- gl_Position = curr + vec4(offset, 0.0, 0.0); // 获取最终节点的 ndc 位置
- // 根据线段节点位置计算颜色插值
- vec4 color = mix(instanceSourceColors, instanceTargetColors, segmentRatio) / 255.;
- vColor = vec4(color.rgb, color.a * opacity);// 获取最终颜色
- // Set color to be rendered to picking fbo (also used to check for selection highlight).
- picking_setPickingColor(instancePickingColors);
- }
来源: https://www.cnblogs.com/dojo-lzz/p/10129152.html