为了账号安全,请及时绑定邮箱和手机立即绑定

根据网页和 SQL 数据库中的坐标创建 svg 元素

根据网页和 SQL 数据库中的坐标创建 svg 元素

交互式爱情 2023-08-10 10:54:31
我有一个类似于在几个html元素上的固定位置之间绘制连接元素的问题,但我正在寻找适用于svg的解决方案(不幸的是,自从我发布问题到现在为止,我没有时间处理这个项目,所以我仍然是网络开发的初学者)。我试图在我的 Web 应用程序中可视化不同 DNA 序列的对齐情况(django 是我的 Web 框架)。每个 DNA 序列都以图表形式显示在<div>. 每个图都包含代表 DNA 序列中子序列的子元素(黄色框)。我通过循环遍历包含从数据库获取的原始值的字典来创建图表。字典是这样构建的:{'1': {'organism': 'sequence 1', 'env_length': 10000, 'frame': '+', 'alns': {'aln_1': {'s_id': 2, 'q_id': 1, 'aln_len': 2700, 'q_start': 1800, 'q_end': 4500, 's_start': 1100, 's_end': 3800}, 'aln_2': {'s_id': 2, 'q_id': 1, 'aln_len': 500, 'q_start': '8000', 'q_end': 8500, 's_start': 7000, 's_end': 7500}}, 'env_genes': {'g1': {'env_name': 'gene_1', 'env_start': 1850, 'env_end': 4700, 'env_strand': '+', 'gene_class': 'normal'}, 'g2': {'env_name': 'gene_2', 'env_start': 8000, 'env_end': 8500, 'env_strand': '+', 'gene_class': 'normal'}}}, '2': {'organism': 'sequence 2', 'env_length': 8500, 'frame': '+', 'alns': {'aln_1': {'s_id': 3, 'q_id': 2, 'aln_len': 2700, 'q_start': 1100, 'q_end': 3800, 's_start': 1, 元素在 html 中是这样创建的:<div class="graph" style='--graph-length: {{value.env_length}}'>          <hr class="line seq">          {% for key2, value2 in value.items %}            {% for key3, value3 in value2.items %}              {% if value3.env_start %}                {% if value3.env_strand == '+' %}                  <span class='env_gene_right {{ value3.gene_class }}' style="--start: {{ value3.env_start }}; --stop: {{ value3.env_end }};"></span>                {% else %}                  <span class='env_gene_left {{ value3.gene_class }}' style="--start: {{ value3.env_start }}; --stop: {{ value3.env_end }};"></span>                {% endif %}              {% endif %}            {% endfor %}          {% endfor %}</div>并使用 css 进行样式设置,如下所示:
查看完整描述

1 回答

?
森栏

TA贡献1810条经验 获得超5个赞

DIV我强烈建议使用单个库来绘制整个图像,而不仅仅是尝试组合s 和图像(这意味着要面对几个对齐和缩放问题)。

我的示例使用 JS 画布库(内置在所有较新的浏览器中),它需要提供将图像绘制到页面中的 JavaScript 所需的所有原始数据:在我的示例中,我使用了单个 JSON input

在开始我的例子之前:

  • 我不得不承认我不明白黄色矩形的含义,我称它们为evidences

  • 我认为在 graph3 中存在一个拼写错误:相似的结束边缘位于 2700,而长度为 2000,在我的示例中,我将长度更改为 4000

  • 我自由地从 graph1 和 graph3 添加了另一对类似的夫妇

const input = {

  graphs: [{

      name: "graph1",

      length: 10000,

      evidences: [{

          from: 100,

          length: 1000

        },

        {

          from: 1800,

          length: 1000

        },

        {

          from: 3000,

          length: 1000

        },

        {

          from: 6000,

          length: 1000

        },

        {

          from: 8000,

          length: 500

        },

        {

          from: 9300,

          length: 500

        },

      ],

    },

    {

      name: "graph2",

      length: 8500,

      evidences: [{

          from: 1100,

          length: 1000

        },

        {

          from: 2500,

          length: 1000

        },

        {

          from: 5000,

          length: 1000

        },

        {

          from: 7000,

          length: 500

        },

      ],

    },

    {

      name: "graph3",

      length: 4000,

      evidences: [{

          from: 1,

          length: 2700

        },

        {

          from: 3200,

          length: 500

        },

      ],

    },

  ],

  similar: [{

      first: 0,

      second: 1,

      from: [1800, 1100],

      length: 2700,

      color: "#8080ff",

    },

    {

      first: 1,

      second: 2,

      from: [1100, 1],

      length: 2700,

      color: "#8080ff",

    },

    {

      first: 0,

      second: 1,

      from: [8000, 7000],

      length: 500,

      color: "#8080ff",

    },

    {

      first: 0,

      second: 2,

      from: [9300, 3200],

      length: 500,

      color: "#ff8080",

    },

  ],

};


const imgScale = window.devicePixelRatio;


function main() {

  // init

  const nameWidth = 100;

  const lengthWidth = 70;

  const graphHight = 50;

  const canvas = document.getElementById("canvas");

  canvas.style.height = graphHight * input.graphs.length + "px";

  const {

    clientWidth,

    clientHeight

  } = canvas;

  const width = (canvas.width = clientWidth * imgScale);

  const height = (canvas.height = clientHeight * imgScale);

  const graphWidth = width - (nameWidth + lengthWidth) * imgScale;

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

  // white fill canvas

  ctx.fillStyle = "#ffffff";

  ctx.fillRect(0, 0, clientWidth, clientHeight);

  // draw each similar

  for (let i = 0; i < input.similar.length; ++i) {

    const {

      first,

      second,

      from,

      length,

      color

    } = input.similar[i];

    const middleFirst = graphHight * (first + 0.5) * imgScale;

    const middleSecond = graphHight * (second + 0.5) * imgScale;

    const fromScaleFirst =

      (from[0] * graphWidth) / input.graphs[first].length;

    const lengthScaleFirst =

      (length * graphWidth) / input.graphs[first].length;

    const fromScaleSecond =

      (from[1] * graphWidth) / input.graphs[second].length;

    const lengthScaleSecond =

      (length * graphWidth) / input.graphs[second].length;

    ctx.fillStyle = color;

    ctx.beginPath();

    ctx.moveTo(nameWidth * imgScale + fromScaleFirst, middleFirst);

    ctx.lineTo(

      nameWidth * imgScale + fromScaleFirst + lengthScaleFirst,

      middleFirst

    );

    ctx.lineTo(

      nameWidth * imgScale + fromScaleSecond + lengthScaleSecond,

      middleSecond

    );

    ctx.lineTo(nameWidth * imgScale + fromScaleSecond, middleSecond);

    ctx.closePath();

    ctx.fill();

    populatePolygons({

      points: [

        [nameWidth * imgScale + fromScaleFirst, middleFirst],

        [

          nameWidth * imgScale + fromScaleFirst + lengthScaleFirst,

          middleFirst,

        ],

        [

          nameWidth * imgScale + fromScaleSecond + lengthScaleSecond,

          middleSecond,

        ],

        [nameWidth * imgScale + fromScaleSecond, middleSecond],

      ],

      object: {

        type: "similar",

        first,

        second,

        from,

        length

      },

    });

  }

  // write each similar edge

  ctx.fillStyle = "#008000";

  ctx.font = "20px Arial";

  ctx.textBaseline = "middle";

  ctx.textAlign = "center";

  for (let i = 0; i < input.similar.length; ++i) {

    const {

      first,

      second,

      from,

      length,

      color

    } = input.similar[i];

    const middleFirst = graphHight * (first + 0.5) * imgScale;

    const middleSecond = graphHight * (second + 0.5) * imgScale;

    const fromScaleFirst =

      (from[0] * graphWidth) / input.graphs[first].length;

    const lengthScaleFirst =

      (length * graphWidth) / input.graphs[first].length;

    const fromScaleSecond =

      (from[1] * graphWidth) / input.graphs[second].length;

    const lengthScaleSecond =

      (length * graphWidth) / input.graphs[second].length;

    ctx.fillText(

      from[0],

      nameWidth * imgScale + fromScaleFirst,

      middleFirst - 20

    );

    ctx.fillText(

      from[0] + length,

      nameWidth * imgScale + fromScaleFirst + lengthScaleFirst,

      middleFirst - 20

    );

    ctx.fillText(

      from[1],

      nameWidth * imgScale + fromScaleSecond,

      middleSecond - 20

    );

    ctx.fillText(

      from[1] + length,

      nameWidth * imgScale + fromScaleSecond + lengthScaleSecond,

      middleSecond - 20

    );

  }

  //draw each graph

  ctx.strokeStyle = "#000000";

  for (let i = 0; i < input.graphs.length; ++i) {

    const graph = input.graphs[i];

    const middle = graphHight * (i + 0.5) * imgScale;

    ctx.beginPath();

    ctx.moveTo(nameWidth * imgScale, middle);

    ctx.lineTo(nameWidth * imgScale + graphWidth, middle);

    ctx.stroke();

    ctx.fillStyle = "#000000";

    ctx.textAlign = "left";

    ctx.fillText(graph.name, 10, middle);

    ctx.textAlign = "right";

    ctx.fillText("1", (nameWidth - 10) * imgScale, middle);

    ctx.fillText(graph.length, width - 10, middle);

    // draw each evidence

    ctx.fillStyle = "#ffff80";

    for (let l = 0; l < graph.evidences.length; ++l) {

      const {

        from,

        length

      } = graph.evidences[l];

      const fromScale = (from * graphWidth) / graph.length;

      const lengthScale = (length * graphWidth) / graph.length;

      ctx.fillRect(

        fromScale + nameWidth * imgScale,

        middle - 10,

        lengthScale,

        20

      );

      ctx.beginPath();

      ctx.rect(

        fromScale + nameWidth * imgScale,

        middle - 10,

        lengthScale,

        20

      );

      ctx.stroke();

      populatePolygons({

        points: [

          [fromScale + nameWidth * imgScale, middle - 10],

          [fromScale + nameWidth * imgScale + lengthScale, middle - 10],

          [fromScale + nameWidth * imgScale + lengthScale, middle + 10],

          [fromScale + nameWidth * imgScale, middle + 10],

        ],

        object: {

          type: "evidence",

          graph: i,

          evidence: l,

        },

      });

    }

  }

}


const polygons = [];


function populatePolygons(polygon) {

  polygons.push(polygon);

}


function inside(point, vs) {

  var x = point[0],

    y = point[1];


  var inside = false;

  for (var i = 0, j = vs.length - 1; i < vs.length; j = i++) {

    var xi = vs[i][0],

      yi = vs[i][1];

    var xj = vs[j][0],

      yj = vs[j][1];


    var intersect =

      yi > y != yj > y && x < ((xj - xi) * (y - yi)) / (yj - yi) + xi;

    if (intersect) inside = !inside;

  }


  return inside;

}


function mouseEvent(kind, event) {

  const {

    offsetX,

    offsetY

  } = event;


  for (polygon in polygons) {

    const {

      object,

      points

    } = polygons[polygon];

    if (inside([offsetX * imgScale, offsetY * imgScale], points))

      interaction(kind, object);

  }

}


function interaction(kind, object) {

  const {

    type,

    graph,

    evidence,

    first,

    second,

    from,

    length

  } = object;


  if (type == "evidence")

    console.log(

      `${kind} on evidence nr ${evidence + 1} of graph nr ${graph + 1}`

    );

  else

    console.log(

      `${kind} on similar between graph nr ${first + 1} from ${

            from[0]

          } and graph nr ${second + 1} from ${from[1]} long ${length}`

    );

}


main();

<canvas id="canvas" style="width: 100%" onclick="mouseEvent('click', event)" onmousemove="mouseEvent('move', event)" />

这将为您提供一个也可以复制到剪贴板的图像;尝试:

  1. 左键单击“运行代码片段”按钮

  2. 右键单击结果图像

  3. 选择“复制图像”上下文菜单(Windows chrome)

  4. 粘贴到任何接受剪贴板图像的程序中

编辑:

根据新的请求(用户需要与图像交互),使用单个库使一切变得更加简单这一点并没有改变。

使用很棒的多边形点算法,我们也可以轻松实现这个更多目标。

我添加了一个简单的控制台日志作为概念验证,您可以随意附加任何其他内容。

不幸的是,console.log 几乎占据了“运行代码片段”的所有空间,以获得更好的体验:

  1. 编辑这个答案

  2. 单击编辑预览中代码段之外的“编辑上述代码段”链接

  3. 按“运行”按钮


查看完整回答
反对 回复 2023-08-10
  • 1 回答
  • 0 关注
  • 94 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信