将列表转换为可缩放的多边形

时间:2017-10-05 19:22:46

标签: javascript css html5 canvas html5-canvas

我使用过乳胶,尤其是tikz。使用这个我能够创建如下所示的图像。

enter image description here

以下短代码用于创建图像。

function hasBirthday(name){
    friends.forEach(function(person){
        if(name === person.name){
        person.age += 1;
        }return person.age;
    }); 
}

我在过去几个小时内尝试使用'HTMLæ,'CSS'和'Javascript'重新创建相同的图像。我使用'canvas'元素绘制线条,但是我遇到了一系列问题,如下图所示

enter image description here

使用以下代码制作。我尽我所能来尽量减少代码。代码可以在帖子的底部找到。代码有以下问题

  • 可扩展性。图像中的文本与页面的“正文”中的文本不同。

  • 图像隐藏了正文中的其余部分

  • 将文字放在图中以外是硬编码的

  • 最后一个小问题是未绘制列表中的第一个元素

我想解决上述问题,但我不确定如何继续。我再次与使用canvas的想法结合在一起(可以使用节点和元素来完成更好的结果)。但是,输出应尽可能地模仿第一个图像。

\documentclass[tikz]{standalone}
\begin{document}
\usetikzlibrary{shapes.geometric}
\usetikzlibrary{backgrounds}

\begin{tikzpicture}[background rectangle/.style={fill=black},
                    show background rectangle]

\def\pages{
Home, 
Events,
Pictures,
Video, 
Contact,
About, 
Map
}

\def\ngon{7}

\node[regular polygon,regular polygon sides=\ngon,minimum size=3cm] (p) {};

\foreach\page [count=\x] in \pages{\node[color=white, shift={(\x*360/7+35:0.4)}] (p\x) at (p.corner \x){\page};}

\foreach\i in {1,...,\numexpr\ngon-1\relax}{
  \foreach\j in {\i,...,\x}{
    \draw[thin, orange, dashed] (p\i) -- (p\j);
  }
}
\end{tikzpicture}

\end{document}

1 个答案:

答案 0 :(得分:2)

使用画布渲染内容

首先我会说使用javascript会比使用像Latex这样的符号表示语言更长。它旨在以最小的麻烦进行图形表示。使其工作的实际代码库是实质性的,但对于普通用户来说是隐藏的。

使用DOM

由于画布的内容存储在DOM中,因此最好在DOM中存储尽可能多的信息,颜色,字体等都可以存储在元素的数据集中。

为此,我将设置放在有序列表中。它包含所有设置,但渲染功能中还有一组默认设置。元素数据集将覆盖默认值,或者您无法添加任何数据集属性,并让它们都使用默认值。

审核设置

在下面的例子中,我只进行了最少的审查。人们倾向于在DOM中的所有内容中加上引号,因为如果表示为字符串,数字有时可能无效,我强制所有数字都为正确的类型。虽然为了安全起见,我应该检查一下它们是否确实是有效数字,其他设置也是如此。我刚认为它们的格式正确。

功能

所有工作都在一个函数中完成,你传递查找列表和画布所需的查询字符串。然后,它使用列表项呈现到画布。

相对大小

由于画布大小并不总是已知(可以通过CSS缩放),因此您需要有一些方法来指定与像素无关的大小。为此,我使用相对大小。因此,字体大小是画布大小的一部分,例如data-font-size = 16表示字体将是画布高度的1/16。线宽相同,短划线大小是线宽的倍数。例如data-line-dash = 4表示短划线的长度是线宽的4倍。

元素的数据属性

要使用数据集,请将属性添加到前缀为单词data-的HTML中的元素,然后将属性名称/ s分隔为"-"。在javascript中,您不能直接将"-"用作变量名称的一部分(它是一个减法运算符),因此属性名称将转换为camelcase(与CSS属性相同),并存储在元素&#39中; s dataset财产。

<!-- HTML -->
<div id="divElement" data-my-Value = "some data"></div>

<script>
    // the property of divElement is available as
    console.log(divElement.dataset.myValue); // output >> "some data"
</script>

缩放&amp;渲染

画布以理想大小(在本例中为512)渲染,但转换设置为确保渲染适合画布。在这个例子中,我缩放了x和y轴)结果是图像没有固定的方面。

背景

默认情况下,画布是透明的,但是如果您重新渲染它,我会清除它。画布下的任何东西都应该是可见的。

我首先渲染线条,然后是文本,清除文本下面的空格以删除线条。 ctx.clearRect确保画布rect是透明的。

画线

要绘制线条,您有两个循环,从每个项目中绘制一条线到每个其他项目。您不想多次绘制一条线,因此内部循环从当前外部循环位置+ 1开始。这样可以确保一条线只渲染一条。

实施例

这个例子展示了我认为你所追求的。我已添加了大量评论,但如果您有任何疑问,请在下面的评论中提出。

我认为您希望有序列表可见。如果不使用CSS规则来隐藏它,则不会影响画布渲染。

此外,如果您通过CSS调整画布大小,则可能会出现画布分辨率和显示大小不匹配的情况。这可能导致像素模糊,并且一些高分辨率显示器会将画布像素设置为大。如果这是一个问题,那么就如何处理模糊的画布渲染和高分辨率显示(如视网膜)有很多答案。

&#13;
&#13;
function drawConnected(listQ, canvasQ) {

  const list = document.querySelector(listQ);
  if(list === null){ 
      console.warn("Could not find list '" + listQ +"'");
      return;
  }
  
  const canvas = document.querySelector(canvasQ);
  if(canvas === null){ 
      console.warn("Could not find canvas '" + canvasQ + "'");
      return;
  }

  const ctx = canvas.getContext("2d");

  const size = 512; // Generic size. This is scaled to fit the canvas
  const xScale = canvas.width / size;
  const yScale = canvas.height / size;

  // get settings or use dsefault
  const settings = Object.assign({
    fontSize : 16,
    lineWidth : 128,
    lineDash : 4,
    textColor : "White",
    lineColor : "#F90", // orange
    startAngle : -Math.PI / 2,
    font : "arial",
  }, list.dataset);

  // calculate relative sizes. convert deg to randians
  const fontSize = size / Number(settings.fontSize) | 0;  // (| 0 floors the value)
  const lineWidth = size / Number(settings.lineWidth) | 0; 
  const lineDash = lineWidth * Number(settings.lineDash);
  const startAngle = Number(settings.startAngle) * Math.PI / 180; // -90 deg is top of screen
  
  // get text in all the list items
  const items = [...list.querySelectorAll("li")].map(element => element.textContent);
  
  // Set up the canvas 
  // Scale the canvas content to fit.
  ctx.setTransform(xScale,0,0,yScale,0,0);
  ctx.clearRect(0,0,size,size);  // clear as canvas may have content
  ctx.font = fontSize + "px " + settings.font;

  // align text to render from its center
  ctx.textAlign = "center";
  ctx.textBaseline = "middle";

  // set the line details
  ctx.lineWidth = lineWidth;
  ctx.lineCap = "round";
  ctx.setLineDash([lineDash, lineDash]);
  
  // need to make room for text so calculate all the text widths
  const widths = [];
  for(let i = 0; i < items.length; i ++){
      widths[i] = ctx.measureText(items[i]).width;
  }

  // use the max width to find a radius that will fit all text
  const maxWidth = Math.max(...widths);
  const radius = (size/2 - maxWidth * 0.6);
  
  // this function returns the x y position on the circle for item at pos
  const getPos = (pos) => {
      const ang = pos / items.length * Math.PI * 2 + startAngle;
      return [
        Math.cos(ang) * radius + size / 2,
        Math.sin(ang) * radius + size / 2
      ];
  };
  
  // draw lines first
  ctx.strokeStyle = settings.lineColor;
  ctx.beginPath();
  for(let i = 0; i < items.length; i ++){
      const [x,y] = getPos(i);
      for(let j = i+1; j < items.length; j ++){
          const [x1,y1] = getPos(j);
          ctx.moveTo(x,y);
          ctx.lineTo(x1,y1);
      }
  }
  ctx.stroke();

  // draw text        
  ctx.fillStyle = settings.textColor;
  for(let i = 0; i < items.length; i ++){
      const [x,y] = getPos(i);
      ctx.clearRect(x - widths[i] * 0.6, y - fontSize * 0.6, widths[i] * 1.2, fontSize * 1.2);
      ctx.fillText(items[i],x,y);
  }

  // restore default transform;   
  ctx.setTransform(1,0,0,1,0,0);
}

// draw the diagram with selector query for ordered list and canvas
drawConnected("#poly","#polygon");
&#13;
* {
    margin: 0;
    padding: 0;
    color:white;
    background:black;
}

canvas {
    display: block;
}

html,
body {
    font-family : arial;
    width: 100%;
    height: 100%;
    margin: 0px;
    border: 0;
    display: block;
}
&#13;
<canvas id="polygon" width = "256" height = "256"></canvas>
    <h2>more space</h2>
    <ol id="poly"
      data-font-size = 16
      data-line-width = 128
      data-line-dash = 2
      data-text-color = "white"
      data-line-color = "#F80"
      data-start-angle = "-90"
      data-font = "arial"
    >
        <li>About</li>
        <li>Home</li>
        <li>Pictures</li>
        <li>Video</li>
        <li>Events</li>
        <li>Map</li>
        <li>Apply?</li>
        <li>Recepies</li>
    </ol>
&#13;
&#13;
&#13;