首先使用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.jsonhtml
_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是一種將連續的值轉化成離散的方法,最終離散成這幾種顏色分類git
這裏仍是使用了實例化的方法,先添加一堆實例化變量:github
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個點來模擬一條拋物線的效果json
_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); }