Javascript在画布中渲染文本并避免文本冲突

时间:2015-08-20 09:21:49

标签: javascript html5-canvas

我正在开发一种映射软件,可以将文本从数据库呈现到画布上的特定坐标。目标是渲染文本不要相互踩(不重叠),但仍然遵循应显示的坐标。这个想法是,如果渲染的文本重叠,程序可以选择以一定角度显示它。目前,我通过以下代码呈现文字:

create_point:function(x,y,stitle){
        var canvas = document.getElementById('text-layer');
        var context = canvas.getContext('2d');
        context.fillText(stitle,x,y); // text and position
        context.save();
}

有关于此的任何想法吗?

提前致谢: - )

1 个答案:

答案 0 :(得分:3)

有趣的头脑拼图!

<强>问题

您已经从数据库中映射了坐标(带有文本标签),有时候两个或多个坐标非常靠近,文本标签相交(导致文本标签不可读)。

一个解决方案

对于要在地图上绘制的每个新文本标签:

  1. 假设每个新文本标签都是在地图坐标的顶部上绘制的。测试新标签是否会覆盖任何现有标签。

    • 如果不进行覆盖,请在顶部绘制(并且已完成此标签)。
    • 如果顶端会导致覆盖,请继续执行第2步。
  2. 重复步骤#1,假设新标签位于地图坐标的右侧

  3. 重复步骤#1,假设新标签位于地图坐标的底边

  4. 重复步骤#1,假设新标签位于地图坐标的左侧

  5. 如果上述所有4个步骤均失败,则在不覆盖现有标签的情况下无法绘制此文本标签。

    如果失败,您必须决定以另一种方式向用户提供文本标签信息。

    我想到了这些选项:

    • 在地图上绘制一个用户可以悬停的小标记,并查看带有文本标签信息的弹出工具提示。这是处理不适合页面的信息的一种非常常见的方式。

    • 在地图上绘制一个小标记,将用户引用到包含文字标签信息的单独图例。

    • 使用箭头线在地图上绘制一个小标记,该箭头线将用户引导至在地图上绘制但距离地图坐标更远的文本标签。

    • 根本不要包含这个新标签!此新标签可能不如现有地图标签重要,因此可能会被省略。您可以通过按照对用户重要性的顺序对地图数据库进行排序来轻松实现此目的。

    以下是演示此解决方案的演示

    enter image description here

    var canvas=document.getElementById("canvas");
    var ctx=canvas.getContext("2d");
    var cw=canvas.width;
    var ch=canvas.height;
    function reOffset(){
      var BB=canvas.getBoundingClientRect();
      offsetX=BB.left;
      offsetY=BB.top;        
    }
    var offsetX,offsetY;
    reOffset();
    window.onscroll=function(e){ reOffset(); }
    
    var fontSize=12;
    var fontFace='verdana';
    var dotRadius=3;
    var legendX=350;
    var legendY=0;
    var legendYincrement=10;
    
    var labels=[];
    var nextId=0;
    
    ctx.textAlign='left';
    ctx.textBaseline='top';
    ctx.font='10px arial';
    ctx.strokeRect(legendX-5,0,cw-legendX+5,ch);
    ctx.fillText('Other labels',legendX-3,legendY+2);
    legendY+=legendYincrement;
    ctx.fillText('(Color Coded)',legendX-3,legendY+2);
    legendY+=legendYincrement;
    
    var label=addLabel('Label #0',cw/2,ch/2,fontSize,fontFace,dotRadius);
    drawLabel(label);
    
    $("#canvas").mousedown(function(e){handleMouseDown(e);});
    
    //
    function addLabel(text,dotX,dotY,fontsize,fontface,dotRadius){
      var font=fontsize+'px '+fontface;
      ctx.font=font;
      var w=ctx.measureText(text).width;
      var h=fontsize*1.286;
      var label={
        id:nextId++,
        text:text,
        x:dotX-w/2,
        y:dotY-dotRadius-h,
        w:w,
        h:h,
        offsetY:0,
        font:font,
        isColliding:false,
        dotRadius:dotRadius,
        dotX:dotX,
        dotY:dotY,
      };
      labels.push(label);
    
      // try to position this new label in a non-colliding position
      var positions=[
        { x:dotX-w/2, y:dotY-dotRadius-h },  // N
        { x:dotX+dotRadius, y:dotY-h/2 },    // E
        { x:dotX-w/2, y:dotY+dotRadius },    // S
        { x:dotX-dotRadius-w, y:dotY-h/2 },  // W
      ];
        for(var i=0;i<positions.length;i++){
        var p=positions[i];
      label.x=p.x;
      label.y=p.y;
      label.isColliding=thisLabelCollides(label);
      if(!label.isColliding){ break; }
    }
    
    //
    return(label);
    }
    
    function handleMouseDown(e){
      // tell the browser we're handling this event
      e.preventDefault();
      e.stopPropagation();
    
      var x=parseInt(e.clientX-offsetX);
      var y=parseInt(e.clientY-offsetY);
    
      var label=addLabel('Label #'+nextId,x,y,fontSize,fontFace,dotRadius)  
    
      drawLabel(label);
    }
    
    //
    function drawLabel(label){
      ctx.textAlign='left';
      ctx.textBaseline='top';
      if(label.isColliding){
        legendY+=legendYincrement;
        ctx.beginPath();
        ctx.arc(legendX,legendY,3,0,Math.PI*2);
        ctx.fillStyle=randomColor();
        ctx.fill();
        ctx.font='10px arial';
        ctx.fillText(label.text,legendX+5,legendY-5);
      }else{
        ctx.font=label.font;
        ctx.fillStyle='black';
        ctx.fillText(label.text,label.x,label.y)
        ctx.strokeRect(label.x,label.y,label.w,label.h);
      }
      ctx.beginPath();
      ctx.arc(label.dotX,label.dotY,label.dotRadius,0,Math.PI*2);
      ctx.fill();
    }
    
    //
    function thisLabelCollides(r1){
      for(var i=0;i<labels.length;i++){
        var r2=labels[i];
        if(r1.id==r2.id || r2.isColliding){continue;}
        var collides=(!(
          r1.x      > r2.x+r2.w ||
          r1.x+r1.w < r2.x      ||
          r1.y      > r2.y+r2.h ||
          r1.y+r1.h < r2.y
        ));
        if(collides){return(true);}
      }
      return(false);
    }
    
    //
    function randomColor(){ 
      return('#'+Math.floor(Math.random()*16777215).toString(16));
    }
    body{ background-color: ivory; }
    #canvas{border:1px solid red; margin:0 auto; }
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
    <h4>Click on the canvas to add more map labels.</h4>
    <canvas id="canvas" width=450 height=300></canvas>