d3.js绘制自定义可配置生信三角阶梯热图

数据要求 必须是N*N,即XY轴数量以及数据必须完全一致

下面是html页面研发的 如果需要封装可以直接将script部分拿出来 xy标题标签字体颜色 方格大小数字显隐等都可以传值控制;

下面是html代码 可以直接拿走右键运行看到效果图 

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>update</title>
    <script src="https://d3js.org/d3.v7.min.js"></script>
  </head>
  <body>
    <svg width="600" height="500"></svg>

    <script>
      const plot_data = {
        matrix: [
          [
            "sample",
            "A3",
            "A2",
            "A1",
            "A4",
            "C2",
            "C3",
            "C1",
            "C4",
            "B1",
            "B2",
            "B3",
            "B4"
          ],
          [
            "Faecalibaculum",
            1.2454201087092798,
            1.1964599688062982,
            1.2246279355528582,
            1.2496145335411446,
            -1.1306832991129674,
            -1.076445172102305,
            -1.1596808814905255,
            -1.0543693651024977,
            -0.12124059145756938,
            -0.10143316578527155,
            -0.12243590396671605,
            80
          ],
          [
            "Bacteroides",
            1.2605676783253026,
            1.339727208620543,
            1.2829076474685954,
            1.1906206576200349,
            -0.23170694303040953,
            -0.22794263012723878,
            -0.21307882520259627,
            80,
            -1.0474900342201063,
            -1.0426278181600128,
            -1.0452839536303442,
            -1.0351560540660434
          ],
          [
            "Cellulophaga",
            1.5851433795025447,
            1.074970812194983,
            1.1582745897376734,
            1.3904893035654822,
            -0.304028853426327,
            -0.33541785723770695,
            -0.42347651429807537,
            -0.39732304180540773,
            -0.9514871367120481,
            -0.9043794729438239,
            -0.941333812654171,
            80
          ],
          [
            "Akkermansia",
            1.3225949238557109,
            1.368432590547057,
            1.3144745952844128,
            1.3219310669554714,
            -0.8782222309203529,
            -0.874643823788357,
            -0.8747870824032014,
            -0.8786077775723006,
            89,
            -0.45841195826897524,
            -0.4596734943761438,
            -0.44500903889116117
          ],
          [
            "Nonlabens",
            1.363808613441155,
            1.3847613250254138,
            1.3780290886888504,
            1.2689644461849265,
            -0.5843329174362808,
            -0.5836076326356598,
            -0.5609224649685922,
            -0.5847146173759441,
            99,
            -0.7704964602309672,
            -0.7704964602309672,
            -0.7704964602309672
          ],
          [
            "Blautia",
            1.4246093983123997,
            1.3686378934537253,
            1.365796150259885,
            1.2160031133875941,
            -0.5180275395251902,
            -0.5008148507877613,
            -0.4887590499819987,
            -0.8034941783271784,
            77,
            -0.7573943595229189,
            -0.7896492014917289,
            -0.7276051677318671
          ],
          [
            "Phocaeicola",
            1.0062914192585513,
            1.5768647964480265,
            1.3954344630454159,
            1.3841428489092116,
            -0.621310052343907,
            -0.6406103415387611,
            -0.7193391664188062,
            -0.5041173016845059,
            -0.7193391664188062,
            -0.7193391664188062,
            -0.7193391664188062,
            -0.7193391664188062
          ],
          [
            "Maribacter",
            1.2082562341007996,
            1.1971510370393013,
            1.5508257240822152,
            1.4318593548656116,
            -0.6936548836947554,
            -0.5325081642246413,
            -0.6936548836947554,
            -0.6936548836947554,
            -0.6936548836947554,
            -0.6936548836947554,
            -0.6936548836947554,
            -0.6936548836947554
          ],
          [
            "Anaerotignum",
            1.338744560804633,
            1.4070616195506538,
            1.1469624299147148,
            1.506239335144494,
            -0.6748759931768119,
            -0.6748759931768119,
            -0.6748759931768119,
            -0.6748759931768119,
            -0.6748759931768119,
            -0.6748759931768119,
            -0.6748759931768119,
            -0.6748759931768119
          ],
          [
            "Sphingobacterium",
            1.1709023258926226,
            1.3434498983751746,
            1.231020601554567,
            1.6386677028848644,
            -0.6730050660884036,
            -0.6730050660884036,
            -0.6730050660884036,
            -0.6730050660884036,
            -0.6730050660884036,
            -0.6730050660884036,
            -0.6730050660884036,
            -0.6730050660884036
          ],
          [
            "Enterococcus",
            -0.6818033249663342,
            -0.6818033249663342,
            -0.6818033249663342,
            97,
            0.8014766051245957,
            -0.6818033249663342,
            -0.5267130341496897,
            -0.6818033249663342,
            -0.6818033249663342,
            1.6986158574231869,
            88,
            00
          ],
          [
            "Prevotella",
            -0.4687733222910701,
            -0.5199902535353341,
            -0.3824132174608811,
            -0.5642733363313597,
            1.4647772102948815,
            1.4385078048046929,
            1.2352943704776311,
            1.1922431111612675,
            -0.8620962243211141,
            77,
            -0.8283283915285544,
            -0.862049938058113
          ]
        ],
        group: [
          {
            group: "AA",
            data: [
              "Faecalibaculum",
              "Bacteroides",
              "Cellulophaga",
              "Akkermansia"
            ]
          },
          {
            group: "B",
            data: ["Nonlabens", "Blautia", "Phocaeicola", "Maribacter"]
          },
          {
            group: "Cccccccc",
            data: [
              "Anaerotignum",
              "Sphingobacterium",
              "Enterococcus",
              "Prevotella"
            ]
          }
        ]
      };

      // 获取标签样式
      function getSvgTextStyle({
        text = "",
        fontSize = 14,
        fontFamily = "Arial",
        fontWeight = "normal"
      } = {}) {
        const svg = d3
          .select("body")
          .append("svg")
          .attr("class", "get-svg-text-style");

        const textStyle = svg
          .append("text")
          .text(text)
          .attr("font-size", fontSize)
          .attr("font-family", fontFamily)
          .attr("font-weight", fontWeight)
          .node()
          .getBBox();

        svg.remove();

        return {
          width: textStyle.width,
          height: textStyle.height
        };
      }

      // 获取离散坐标轴宽高
      function getSvgBandAxisStyle({
        fontSize = 20,
        orient = "bottom",
        fontFamily = "Arial",
        fontWeight = "normal",
        rotate = 0,
        domain = ["A", "B", "C"],
        range = [0, 200]
      } = {}) {
        let axis;
        let svg = d3
          .select("body")
          .append("svg")
          .attr("width", 200)
          .attr("height", 100)
          .attr("transform", "translate(300, 200)")
          .attr("class", "get-svg-axis-style");

        let scale = d3.scaleBand().domain(domain).range(range);

        if (orient === "bottom" || orient === "top") {
          axis = d3.axisBottom(scale);
        } else {
          axis = d3.axisLeft(scale);
        }

        let axisStyle = svg
          .append("g")
          .call(axis)
          .call((g) => {
            g.selectAll("text")
              .attr("fill", "#555")
              .attr("font-size", fontSize)
              .attr("font-family", fontFamily)
              .attr("font-weight", fontWeight)
              .attr(
                "tmpY",
                g.select("text").attr("tmpY") || g.select("text").attr("dy")
              )
              .attr(
                "dy",
                rotate > 70 && rotate <= 90
                  ? "0.35em"
                  : rotate >= -90 && rotate < -70
                  ? "0.4em"
                  : g.select("text").attr("tmpY")
              )
              .attr(
                "text-anchor",
                orient === "left"
                  ? "end"
                  : rotate
                  ? rotate > 0
                    ? "start"
                    : "end"
                  : "middle"
              )
              .attr(
                "transform",
                `translate(0, 0) ${
                  rotate
                    ? `rotate(${rotate} 0 ${g.select("text").attr("y")})`
                    : ""
                }`
              );
          })
          .node()
          .getBBox();

        svg.remove();
        return {
          width: axisStyle.width,
          height: axisStyle.height
        };
      }

      let colors = [
          "#FF735A",
          "#5BCCB6",
          "#4782B3",
          "#EB7BC0",
          "#FFAA33",
          "#FED840",
          "#AB80F5",
          "#EACC93",
          "#5C9966",
          "#A0C896",
          "#EB8D8E",
          "#CEAFAF"
        ],
        main_title = "FastANI ANI values(clustered by ANI)",
        main_title_color = "#000",
        main_title_font = "Arial",
        main_title_size = 14,
        x_title = "x title",
        x_title_color = "#000",
        x_title_font = "Arial",
        x_title_size = 14,
        x_text_color = "#000",
        x_text_font = "Arial",
        x_text_size = 14,
        x_text_rotate = -60,
        y_title = "y title",
        y_title_color = "#000",
        y_title_font = "Arial",
        y_title_size = 14,
        y_text_color = "#000",
        y_text_font = "Arial",
        y_text_size = 14,
        y_text_rotate = 0,
        y_text_style = "normal",
        legend_title = "legend title",
        legend_title_color = "#000000",
        legend_title_size = 14,
        legend_title_font = "Arial",
        legend_text_color = "#000000",
        legend_text_size = 12,
        legend_text_font = "Arial",
        numVisable = true,
        numSize = 8,
        cellSize = 30;

      const speciesInfo = plot_data.matrix.slice(1).map((row) => row[0]);
      const heatmapData = plot_data.matrix.slice(1).map((row) => row.slice(1));

      // 主标题宽高
      const mainTitleW = getSvgTextStyle({
        text: main_title,
        fontSize: main_title_size,
        fontFamily: main_title_font
      }).width;
      const mainTitleH = getSvgTextStyle({
        text: main_title,
        fontSize: main_title_size,
        fontFamily: main_title_font
      }).height;

      // X轴及标题高度
      const xTitleH = getSvgTextStyle({
        text: x_title,
        fontSize: x_title_size,
        fontFamily: x_title_font
      }).height;
      const xTitleW = getSvgTextStyle({
        text: x_title,
        fontSize: x_title_size,
        fontFamily: x_title_font
      }).width;

      const xAxisH = getSvgBandAxisStyle({
        fontSize: x_text_size,
        fontFamily: x_text_font,
        rotate: x_text_rotate,
        domain: speciesInfo
      }).height;

      // Y轴及标题高度
      const yTitleH = getSvgTextStyle({
        text: y_title,
        fontSize: y_title_size,
        fontFamily: y_title_font
      }).height;
      const yTitleW = getSvgTextStyle({
        text: y_title,
        fontSize: y_title_size,
        fontFamily: y_title_font
      }).width;

      const yAxisW = getSvgBandAxisStyle({
        fontSize: y_text_size,
        fontFamily: y_text_font,
        domain: speciesInfo,
        orient: "left"
      }).width;
      const yAxisH = getSvgBandAxisStyle({
        fontSize: y_text_size,
        fontFamily: y_text_font,
        domain: speciesInfo
      }).height;

      // 图例
      const legendTitleW = getSvgTextStyle({
        text: legend_title,
        fontSize: legend_title_size,
        fontFamily: legend_title_font
      }).width;
      const legendTitleH = getSvgTextStyle({
        text: legend_title,
        fontSize: legend_title_size,
        fontFamily: legend_title_font
      }).height;

      // n,n-1,n-2,n-...,1 数据处理
      const N = heatmapData[heatmapData.length - 1].length;

      // 计算所有矩形的数据
      let allRects = [];
      for (let rowIndex = 0; rowIndex < heatmapData.length; rowIndex++) {
        // 每行矩形数量,从N递减到1(或0,取决于行数)
        const numRects = Math.min(N - rowIndex, heatmapData[rowIndex].length);
        for (let colIndex = 0; colIndex < numRects; colIndex++) {
          allRects.push({
            value: heatmapData[rowIndex][colIndex],
            rowIndex,
            colIndex
          });
        }
      }

      const margin = { top: 30, right: 30, bottom: 30, left: 30 };

      const width =
        margin.left +
        margin.right +
        yTitleH +
        yAxisW +
        cellSize * N +
        legendTitleH +
        100;
      const height =
        margin.top +
        margin.bottom +
        mainTitleH +
        cellSize * N +
        xAxisH +
        xTitleH;
      100;

      const svg = d3.select("svg").attr("width", width).attr("height", height);

      const chartWidth = cellSize * N;
      const chartHeight = cellSize * N;

      // 缩放和轴
      const xScale = d3
        .scaleBand()
        .domain(d3.range(heatmapData[0].length))
        .range([0, chartWidth])
        .padding(0.02);

      const yScale = d3
        .scaleBand()
        .domain(d3.range(heatmapData.length))
        .range([chartHeight, 0])
        .padding(0.02);

      const xAxis = d3.axisBottom(xScale).tickFormat((i) => speciesInfo[i]);
      const yAxis = d3.axisLeft(yScale).tickFormat((i) => speciesInfo[i]);

      // 创建一个根据索引映射到颜色数组的比例尺
      const colorScale = d3
        .scaleOrdinal()
        .domain(d3.range(colors.length))
        .range(colors);

      // 绘制X、Y轴
      svg
        .append("g")
        .attr("class", "axis x-axis")
        .attr(
          "transform",
          `translate(${margin.left + yAxisW + yTitleH + 20}, ${
            margin.top + mainTitleH + chartHeight + 10
          })`
        )
        .call(xAxis)
        .call((g) => {
          g.selectAll("text")
            .attr("fill", x_text_color)
            .attr("font-size", x_text_size)
            .attr("font-family", x_text_font)
            .attr(
              "tmpY",
              g.select("text").attr("tmpY") || g.select("text").attr("dy")
            )
            .attr(
              "dy",
              x_text_rotate > 70 && x_text_rotate <= 90
                ? "0.35em"
                : x_text_rotate >= -90 && x_text_rotate < -70
                ? "0.4em"
                : g.select("text").attr("tmpY")
            )
            .attr(
              "text-anchor",
              x_text_rotate ? (x_text_rotate > 0 ? "start" : "end") : "middle"
            )
            .attr(
              "transform",
              `translate(0, 0) ${
                x_text_rotate
                  ? `rotate(${x_text_rotate} 0 ${g.select("text").attr("y")})`
                  : ""
              }`
            );
        });

      svg
        .append("g")
        .attr("class", "axis y-axis")
        .attr(
          "transform",
          `translate(${margin.left + yAxisW + yTitleH}, ${
            margin.top + mainTitleH - 10
          })`
        )
        .call(yAxis)
        .selectAll(".tick text")
        .attr("fill", y_text_color)
        .attr("font-size", y_text_size)
        .attr("font-family", y_text_font);

      // 绘制热图
      const heatmapGroup = svg
        .append("g")
        .attr(
          "transform",
          `translate(${margin.left + yAxisW + yTitleH + 20},${
            margin.top + mainTitleH - 10
          })`
        );

      // 绘制矩形
      heatmapGroup
        .selectAll("rect")
        .data(allRects)
        .enter()
        .append("rect")
        .attr("x", (d) => xScale(d.colIndex))
        .attr("y", (d) => yScale(d.rowIndex))
        .attr("width", cellSize)
        .attr("height", cellSize)
        .attr("rx", 5)
        .attr("ry", 5)
        .style("fill", (d) => colorScale(Math.floor(d.value * colors.length)))
        .style("cursor", "pointer")
        .on("mouseover", function (d, i) {
          const colIndex = i.colIndex;
          const rowIndex = i.rowIndex;

          const yLabel = speciesInfo[rowIndex];
          const xLabel = speciesInfo[colIndex];

          heatmapGroup.selectAll("rect").filter(function (e, j) {
            return !(
              j % heatmapData[0].length === colIndex &&
              Math.floor(j / heatmapData[0].length) === rowIndex
            );
          });
          // .style("opacity", 0.5);

          // 显示提示框
          let tooltip = d3.select("body").select(".scatter-tooltip");
          if (tooltip.empty()) {
            // 如果提示框不存在,则创建它
            tooltip = d3
              .select("body")
              .append("div")
              .attr("class", "scatter-tooltip")
              .style("position", "absolute")
              .style("pointer-events", "none")
              .style("opacity", 0);
          }

          // 更新提示框的内容和位置
          tooltip
            .html(
              `<div>X:${xLabel}</br>
                      Y:${yLabel}</br>
                      值: ${i.value}</div>`
            )
            .style("left", `${event.pageX + 10}px`)
            .style("top", `${event.pageY + 10}px`)
            .style("padding", "7px 5px")
            .style("border-radius", "4px")
            .style("font-size", "12px")
            .style("color", "#555")
            .style("background", "rgba(255, 255,  255, .8)")
            .style("border", `1px solid rgba(0, 0,  0, .5)`)
            .style("opacity", 1);

          d3.select(this).style("opacity", 1);
        })
        .on("mouseout", function () {
          // 恢复所有矩形的透明度
          // heatmapGroup.selectAll("rect").style("opacity", 1);
          // 隐藏提示
          d3.select(".scatter-tooltip").remove();
        });

      // 绘制数字标签
      if (numVisable) {
        heatmapGroup
          .selectAll("text.text-label")
          .data(allRects)
          .enter()
          .append("text")
          .attr("class", "text-label")
          .attr("font-size", numSize)
          .attr("text-anchor", "middle")
          .attr("dominant-baseline", "central")
          .attr("x", (d) => xScale(d.colIndex) + xScale.bandwidth() / 2)
          .attr("y", (d) => yScale(d.rowIndex) + yScale.bandwidth() / 2)
          .text((d) => d.value.toFixed(2));
      }

      // 条状物种信息
      let groupColors = {};

      // 遍历group数组
      plot_data.group.forEach((groupItem, index) => {
        if (index < colors.length) {
          groupColors[groupItem.group] = colors[index];
        } else {
          groupColors[groupItem.group] = colors[index % colors.length];
        }
      });

      // 辅助函数:根据物种名返回分组名
      function getGroupForSpecies(species) {
        for (let grp of plot_data.group) {
          if (grp.data.includes(species)) {
            return grp.group;
          }
        }
        // 如果物种不在任何分组中,可以返回一个默认值或抛出错误
        return null;
      }

      // 绘制条形图
      // 绘制X轴条形图
      const xAxisBars = svg
        .append("g")
        .attr("class", "x-axis-bars")
        .attr(
          "transform",
          `translate(${margin.left + yAxisW + yTitleH + 20}, ${
            margin.top + mainTitleH + chartHeight - 5
          })`
        );
      // 遍历物种信息
      speciesInfo.forEach((species, index) => {
        // 获取物种所属分组的颜色
        const groupIndex = getGroupForSpecies(species);
        const color = groupIndex ? groupColors[groupIndex] : "#ccc"; // 如果没有找到分组,则使用默认颜色

        // 绘制条形图
        xAxisBars
          .append("rect")
          .attr("x", xScale(index))
          .attr("y", 0)
          .attr("width", xScale.bandwidth())
          .attr("height", 8)
          .attr("rx", 5)
          .attr("ry", 5)
          .attr("fill", color);
      });

      // 创建一个用于绘制y轴条形的g元素
      const yAxisBars = svg
        .append("g")
        .attr("class", "y-axis-bars")
        .attr(
          "transform",
          `translate(${margin.left + yAxisW + yTitleH + 7}, ${
            margin.top + mainTitleH - 10
          })`
        );

      // 遍历物种信息
      speciesInfo.forEach((species, index) => {
        const groupIndex = getGroupForSpecies(species);
        const color = groupIndex ? groupColors[groupIndex] : "#ccc"; // 如果没有找到分组,则使用默认颜色

        // 绘制条形
        yAxisBars
          .append("rect")
          .attr("x", 0)
          .attr("y", yScale(index))
          .attr("width", 8) // 条形的宽度
          .attr("height", yScale.bandwidth())
          .attr("rx", 5)
          .attr("ry", 5)
          .attr("fill", color);
      });

      // 添加主标题
      svg
        .append("g")
        .attr(
          "transform",
          `translate(${chartWidth / 2 + yAxisW + yTitleH + margin.left},${
            margin.top - 5
          })`
        )
        .append("text")
        .attr("text-anchor", "middle")
        .text(main_title)
        .attr("fill", main_title_color)
        .attr("font-size", main_title_size)
        .attr("font-family", main_title_font);

      // X轴标题
      svg
        .append("g")
        .attr("class", "svg-x-title")
        .append("text")
        .text(x_title)
        .attr("fill", x_title_color)
        .attr("font-family", x_title_font)
        .attr("font-size", x_title_size)
        .attr("text-anchor", "middle")
        .attr("dominant-baseline", "hanging")
        .attr(
          "transform",
          `translate(${chartWidth / 2 + yAxisW + yTitleH + margin.left}, ${
            margin.top + mainTitleH + chartHeight + xAxisH + 20
          })`
        );

      // Y轴标题
      svg
        .append("g")
        .attr("class", "svg-y-title")
        .append("text")
        .text(y_title)
        .attr("fill", y_title_color)
        .attr("font-family", y_title_font)
        .attr("font-size", y_title_size)
        .attr("text-anchor", "middle")
        .attr("dominant-baseline", "hanging")
        .attr(
          "transform",
          `translate(${margin.left - 10}, ${
            margin.top + mainTitleH + chartHeight / 2
          }) rotate(-90)`
        );

      // 图例
      const legend_container = svg
        .append("g")
        .attr("class", "legend-container")
        .attr(
          "transform",
          `translate(${margin.left + yTitleH + yAxisW + chartWidth + 40},${
            margin.top + mainTitleH + 10
          })`
        );

      legend_container
        .append("g")
        .attr("class", "legend-title-container")
        .append("text")
        .attr("x", -5)
        .attr("y", 5)
        .attr("class", "legend-title")
        .text(legend_title)
        .attr("fill", legend_title_color)
        .attr("font-family", legend_title_font)
        .attr("font-size", legend_title_size);

      const defs = legend_container.append("defs");

      const linearGradient = defs
        .append("linearGradient")
        .attr("id", "linearColor")
        .attr(
          "transform",
          `translate(${width - margin.right - legendTitleW},${
            margin.top + mainTitleH + 20
          })`
        )
        .attr("x1", "0%")
        .attr("y1", "0%")
        .attr("x2", "0%")
        .attr("y2", "100%");

      linearGradient
        .append("stop")
        .attr("offset", "0%")
        .style("stop-color", colors[2]);

      linearGradient
        .append("stop")
        .attr("offset", "50%")
        .style("stop-color", colors[1]);

      linearGradient
        .append("stop")
        .attr("offset", "100%")
        .style("stop-color", colors[0]);

      legend_container
        .append("rect")
        .attr("x", 0)
        .attr("y", 20)
        .attr("width", 10)
        .attr("height", 70)
        .style("fill", "url(#" + linearGradient.attr("id") + ")");

      //颜色图例值
      // const data_tip = [
      //   d3.min(allRects, (d) => {
      //     return Number(d.value);
      //   }),
      //   d3.median(allRects, (d) => {
      //     return Number(d.value);
      //   }),
      //   d3.max(allRects, (d) => {
      //     return Number(d.value);
      //   })
      // ];

      // const data_tip_backup = [];
      // data_tip.map((item) => {
      //   data_tip_backup.push(Number(item).toFixed(2));
      // });
      const values = allRects.map((d) => Number(d.value));
      values.sort((a, b) => a - b);

      // 计算分位数的索引
      const n = values.length;
      const q1Index = Math.floor((n - 1) * 0.25); // 第一四分位数
      const q3Index = Math.floor((n - 1) * 0.75); // 第三四分位数

      // 使用d3.quantile(但这里我们实际上使用排序后的数组索引)或直接索引来获取值
      const data_tip = [
        d3.min(values), // 最小值
        values[q1Index], // 第一四分位数
        d3.median(values), // 中位数
        values[q3Index], // 第三四分位数
        d3.max(values) // 最大值
      ];
      const data_tip_backup = data_tip.map((item) => item.toFixed(2));

      legend_container
        .append("g")
        .selectAll(".ledend_text")
        .data(data_tip_backup)
        .enter()
        .append("text")
        .attr("class", "legend-text")
        .text((d) => {
          return d;
        })
        .attr("x", 15)
        .attr("y", (_, i) => {
          if (i === 0) {
            return 90;
          } else if (i === 1) {
            return 75;
          } else if (i === 2) {
            return 60;
          } else if (i === 3) {
            return 44;
          } else {
            return 28;
          }
        })
        .attr("text-anchor", "start")
        .attr("fill", "#000000")
        .attr("font-size", 11)
        .attr("font-family", 11);

      const group_container = legend_container
        .append("g")
        .attr("class", "group-container")
        .attr("transform", `translate(0,110)`);

      let groupArr = [];
      plot_data.group.forEach((item) => {
        groupArr.push(item.group);
      });

      group_container
        .selectAll(".group-rect")
        .data(groupArr)
        .enter()
        .append("rect")
        .attr("x", 0)
        .attr("y", (_, i) => {
          return i * 13;
        })
        .attr("width", 20)
        .attr("height", 8)
        .attr("rx", 5)
        .attr("ry", 5)
        .attr("fill", (_, i) => colors[i % colors.length]);

      group_container
        .append("g")
        .selectAll(".ledend_text")
        .data(groupArr)
        .enter()
        .append("text")
        .attr("class", "legend-text")
        .text((d) => {
          return d;
        })
        .attr("x", 27)
        .attr("y", (_, i) => {
          const legendOffsetY = 7;
          return i * 14 + legendOffsetY;
        })
        .attr("text-anchor", "start")
        .attr("fill", legend_text_color)
        .attr("font-size", legend_text_size)
        .attr("font-family", legend_text_font);
    </script>
  </body>
</html>

与上篇文章d3.js绘制自定义可配置生信正方形热图-CSDN博客绘图方法基本一致,但数据做了特殊的处理,因为实现最下面一行画N个,往上一行N-1个,再往上N-2个,以此类推知道最上面剩下一个,所以N*N的数据要做一定的处理,上面代码块中包含这个数据处理;

本篇代码里面没有d3.js绘制自定义可配置生信正方形热图-CSDN博客这篇文章对数字和方块颜色的额外处理,不做处理的话可以参考本文;
同样也是svg画布的宽高是根据数据量以及画的图的大小来算的,不用担心哪部分位置不够这些问题;
plot_data自拟假数据,做了上述每行N,N-1,N-2,.......,1的处理;
图例,左边下边的条状等同d3.js绘制自定义可配置生信正方形热图-CSDN博客
其实两个绘图我感觉是可以合并的,因为绘图方法都大差不差,额外的处理可以做判断,但是我累了就不合并了哈哈哈哈哈哈

效果展示:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值