首页 > 其他分享 >使用fabric.js根据坐标生成svg图,并使用echarts显示

使用fabric.js根据坐标生成svg图,并使用echarts显示

时间:2024-04-15 16:33:48浏览次数:22  
标签:canvas const fabric img svg js images data

仍然是 在图片上特定区域根据数值显示不同的颜色 的需求。拖了这么久,最终的解决方案终于定下来了:使用aoi检测设备导出的坐标来标定需显示数值和颜色的区域,如此一来就不需要人操作UI界面来标定数值的显示区域。

最终使用echarts显示的方法有2种:

  • 地图map+使用坐标标记区域且区域有name属性的svg,根据区域的name属性显示对应的值
  • 图片直接转成svg图作为地图+地理坐标系+散点图

地图+区域有name属性的svg

先将背景图放入images文件夹中,利用上传的xlsx文件中的坐标标记背景图的区域,最后打包下载svg图和背景图。

<script src="../plugins/fabric.min.js"></script>
<script src="../plugins/jquery/jquery-3.3.1.js"></script>
<script type="text/javascript" src="../plugins/jszip.min.js"></script>
<script src="../plugins/FileSaver.js"></script>
<script src="../plugins/xlsx.full.min.js"></script>
<input type="file" name="file" id="uploadFile" size="10" onchange="readFile(this);" />
<canvas id="example" style="border: solid 1px #ccc"></canvas>
<script>
  function handleSvg(data) {
    const img_folder_path = "../images/";
    const local_url = "http://localhost:3000/html-demos/images/";
    const replace_url = encodeURI(`http://localhost:3000/html-demos/images/`);

    console.log(data);
    Promise.all(
      Object.keys(data)
        .map((filename, idx) => {
          return new Promise((resolve, reject) => {
            const img_path = img_folder_path + filename + ".jpg";
            fabric.Image.fromURL(img_path, (bg_img) => {
              const canvas = new fabric.StaticCanvas("example");
              canvas.setBackgroundImage(bg_img, canvas.renderAll.bind(canvas));
              canvas.setWidth(bg_img.width);
              canvas.setHeight(bg_img.height);

              const coord_arr = data[filename]["coords"];
              const name_arr = data[filename]["names"];
              const area_width = 20;
              const area_height = 20;
              for (let i in coord_arr) {
                const rect = new fabric.Rect({
                  left: coord_arr[i][0] - area_width / 2,
                  top: coord_arr[i][1] - area_height / 2,
                  originX: "left",
                  originY: "top",
                  width: area_width,
                  height: area_height,
                  fill: "rgba(255, 0, 0, 1)",
                });
                rect.toSVG = (function (toSVG) {
                  return function () {
                    const svgString = toSVG.call(this);
                    const domParser = new DOMParser();
                    const doc = domParser.parseFromString(svgString, "image/svg+xml");
                    let type = this.type;
                    const parentG = doc.querySelector(`${type}`);
                    parentG.setAttribute("name", name_arr[i]);
                    return doc.documentElement.outerHTML;
                  };
                })(rect.toSVG);
                canvas.add(rect);
              }
              const svgString = canvas.toSVG(null, function (svg) {
                return svg.replace(local_url, replace_url);
              });
              resolve({ [filename + ".svg"]: svgString });
            });
          });
        })
        .concat(
          Object.keys(data).map((filename, idx) => {
            return new Promise((resolve, reject) => {
              const img = new Image();
              img.crossOrigin = "Anonymous"; // 图片可以跨域访问
              img.onload = () => {
                const canvas = document.createElement("canvas");
                canvas.width = img.width;
                canvas.height = img.height;
                const ctx = canvas.getContext("2d");
                ctx.drawImage(img, 0, 0);
                canvas.toBlob((blob) => {
                  resolve({ [filename + ".jpg"]: blob });
                });
              };
              img.onerror = reject;
              img.src = img_folder_path + filename + ".jpg";
            });
          })
        )
    )
      .then((images) => {
        const zip = new JSZip();
        images.forEach((image) => {
          Object.keys(image).forEach((key) => {
            zip.file(key, image[key]);
          });
        });
        zip.generateAsync({ type: "blob" }).then((blob) => {
          saveAs(blob, "svg.zip");
        });
      })
      .catch((err) => {
        console.error("Error packaging images:", err);
      });
  }

  function readFile() {
    const file = $("#uploadFile")[0].files[0];
    const fileTypes = ["application/vnd.ms-excel", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"];
    if (fileTypes.indexOf(file.type) === -1) {
      alert("文件类型错误");
      return;
    }
    const reader = new FileReader();
    const data_res = {};
    reader.onload = function (e) {
      const data = e.target.result;
      const workbook = XLSX.read(data, { type: "binary", cellDates: true });
      const sheetNames = workbook.SheetNames;
      const worksheet = workbook.Sheets[sheetNames[0]];
      const res = XLSX.utils.sheet_to_json(worksheet);
      for (let i in res) {
        const values = Object.values(res[i]);
        if (values.length < 4) continue;
        // const key = values[0] + "_" + values[1];
        const key = values[0];
        if (key in data_res) {
          data_res[key]["coords"].push(values[3].split(","));
          data_res[key]["names"].push(values[2]);
        } else {
          data_res[key] = {
            coords: [values[3].split(",")],
            names: [values[2]],
          };
        }
      }
      handleSvg(data_res);
    };
    reader.readAsBinaryString(file);
  }
</script>

 文件格式如下:

 生成的zip文件解压如下:

使用echarts加载svg图并填入数值

<script src="../plugins/echarts.min.v5.4.2.js"></script>
<script src="../plugins/jquery/jquery-3.3.1.js"></script>
<body>
  <div id="mapChart" style="height: 400px"></div>
  <script>
    function drawChart() {
      var chartDom = document.getElementById("mapChart");
      var myChart = echarts.init(chartDom);
      var option;

      $.get("../images/a.svg", function (svg) {
        echarts.registerMap("svg_map", { svg: svg });
        option = {
          tooltip: {},
          visualMap: {
            left: "left",
            top: 10,
            min: 0,
            max: 100,
            orient: "horizontal",
            text: ["", "Value"],
            realtime: true,
            calculable: true,
            inRange: {
              color: ["#0732FC", "#F92606"],
            },
          },
          series: [
            {
              name: "Value",
              type: "map",
              map: "svg_map",
              roam: true,
              emphasis: {
                label: {
                  show: false,
                },
              },
              selectedMode: false,
              data: [{ name: "aa", value: 95 }],
            },
          ],
        };
        myChart.setOption(option);
      });

      option && myChart.setOption(option);
    }

    drawChart();
  </script>
</body>

 

 svg图+地理坐标系+散点图 

<script src="../plugins/fabric.min.js"></script>
<script src="../plugins/jquery/jquery-3.3.1.js"></script>
<script type="text/javascript" src="../plugins/jszip.min.js"></script>
<script src="../plugins/FileSaver.js"></script>
<canvas id="example" style="border: solid 1px #ccc"></canvas>
<script>
  function handleSvg(data) {
    const img_folder_path = "../images/";
    const local_url = "http://localhost:3000/html-demos/images/";
    const replace_url = encodeURI(`http://localhost:3000/html-demos/images/`);

    console.log(data);
    Promise.all(
      data
        .map((filename, idx) => {
          return new Promise((resolve, reject) => {
            const img_path = img_folder_path + filename + ".jpg";
            fabric.Image.fromURL(img_path, (bg_img) => {
              const canvas = new fabric.StaticCanvas("example");
              canvas.setBackgroundImage(bg_img, canvas.renderAll.bind(canvas));
              canvas.setWidth(bg_img.width);
              canvas.setHeight(bg_img.height);

              const svgString = canvas.toSVG(null, function (svg) {
                return svg.replace(local_url, replace_url);
              });
              resolve({ [filename + ".svg"]: svgString });
            });
          });
        })
        .concat(
          data.map((filename, idx) => {
            return new Promise((resolve, reject) => {
              const img = new Image();
              img.crossOrigin = "Anonymous"; // 图片可以跨域访问
              img.onload = () => {
                const canvas = document.createElement("canvas");
                canvas.width = img.width;
                canvas.height = img.height;
                const ctx = canvas.getContext("2d");
                ctx.drawImage(img, 0, 0);
                canvas.toBlob((blob) => {
                  resolve({ [filename + ".jpg"]: blob });
                });
              };
              img.onerror = reject;
              img.src = img_folder_path + filename + ".jpg";
            });
          })
        )
    )
      .then((images) => {
        const zip = new JSZip();
        images.forEach((image) => {
          Object.keys(image).forEach((key) => {
            zip.file(key, image[key]);
          });
        });
        zip.generateAsync({ type: "blob" }).then((blob) => {
          saveAs(blob, "svg.zip");
        });
      })
      .catch((err) => {
        console.error("Error packaging images:", err);
      });
  }

  handleSvg(["a", "b"]);
</script>

 使用echarts的geo坐标系显示数值区域

<script src="../plugins/echarts.min.js"></script>
<script src="../plugins/jquery/jquery-3.6.0.min.js"></script>
<div style="width: 100%; height: 500px" id="main"></div>
<script>
  const ROOT_PATH = "http://localhost:3000/html-demos/images/";

  const chartDom = document.getElementById("main");
  const myChart = echarts.init(chartDom);
  let option;

  $.get(ROOT_PATH + "b.svg", function (svg) {
    echarts.registerMap("svg_map", { svg: svg });
    option = {
      tooltip: {
        formatter: (params) => {
          return `${params.seriesName} <br/>
                  ${params.marker}Value:${params.value[2]}`;
        },
      },
      geo: {
        tooltip: {
          show: true,
        },
        map: "svg_map",
        roam: true,
      },
      series: [
        {
          type: "scatter",
          name: "aa",
          coordinateSystem: "geo",
          geoIndex: 0,
          symbol: "rect",
          symbolSize: 10,
          itemStyle: {
            color: "#b02a02",
          },
          encode: {
            tooltip: 2,
          },
          data: [
            [100, 200, 95],
            [150, 150, 30],
          ],
        },
      ],
    };
    myChart.setOption(option);
  });

  option && myChart.setOption(option);
</script>

总的来说:使用geo坐标系比较方便,在本例中的缺点需要自己计算颜色,且series中很多项(不同的数值显示不同颜色)。

另外需注意svg图的加载问题:svg图占用空间较小,svg图加载到页面上后,会继续加载svg文件中引用的图片,因此一定要保证图片可访问。

 

标签:canvas,const,fabric,img,svg,js,images,data
From: https://www.cnblogs.com/caroline2016/p/18136311

相关文章

  • JSNice:Predicting Program Properties from “Big Code”
    发表:ACMSIGPLANNotices,2015,苏黎世联邦理工学院计算机科学系SoftwareReliabilityLab,AndreasKrause团队(https://scholar.google.com/citations?user=eDHv58AAAAAJ)(https://www.sri.inf.ethz.ch/research/plml)工具:http://jsnice.org内容概括  文章通过“大代码”......
  • js获取元素位置
    js获取元素位置JavaScript中获取元素位置的方法有以下几种实现方式: 使用getBoundingClientRect()方法:constelement=document.getElementById('elementId');constrect=element.getBoundingClientRect();constposition={top:rect.top,lef......
  • 无痛接入flow.js
    前言:针对项目越来越大,js弱类型、动态类型捉襟见肘,导致项目维护成本越来越高,typeScript接入学习成本及vue兼容性等问题,考虑引入flow.js优点:静态类型检查,避免类型编码错误不影响线上代码打包,对线上代码影响小接入后可仅在需要的文件中使用项目接入流程如下:使用yarn、babel的......
  • jsoncpp的基本操作
    基本概念: 2.jsoncpp的使用jsoncpp库中的类被定义到了一个Json命名空间中,建议在使用这个库的时候先声明这个命名空间: usingnamespaceJson;使用jsoncpp库解析json格式的数据,我们只需要掌握三个类:Value类:将json支持的数据类型进行了包装,最终得到一个Value类型FastWrite......
  • js获取时间差,返回格式为01天02小时03秒
    //获取时间差返回值格式:01天02小时30秒exportfunctioncaclulateDiffTime(start,end):string{start=newDate(start).getTime();end=newDate(end).getTime();letstaytimeGap=end-start;if(staytimeGap<0){staytimeGap=start-end;}i......
  • 30 天精通 RxJS (19):实践范例 - 简易 Auto Complete 实作
    今天我们要做一个RxJS的经典范例-自动完成(AutoComplete),自动完成在实务上的应用非常广泛,几乎随处可见这样的功能,只要是跟表单、搜寻相关的都会看到。虽然是个很常见的功能,但多数的工程师都只是直接套套件来完成,很少有人会自己从头到尾把完整的逻辑写一次。如果有自己......
  • 30 天精通 RxJS (18):可观察运算符 - switchMap, mergeMap, concatMap
    今天我们要讲三个非常重要的operators,这三个operators在很多的RxJS相关的library的使用示例上都会看到。很多初学者在使用这些library时,看到这三个operators很可能就放弃了,但其实如果有把这个系列的文章完整看过的话,现在应该就能很好接受跟理解。OperatorsconcatMapconcat......
  • js混淆简介
      JS混淆技术通过降低JS程序的可读性来对网站进行取证分析。JavaScript(JS)混淆在流行网站中非常普遍。最近的研究表明,Alexa前10万个网站中95.90%的网站至少包含一个混淆的JS程序,混淆的JS程序给各种任务带来了挑战。JS混淆技术通过降低JS程序的可读性来阻止对网站源......
  • 如何解决node.js运行mysql报错?
    首先检查mysql客户端的密码是否正确正确后提示我没有安装mysql模板 在文件目录终端下输入cnpminstall-gmysql进行安装这时候运行还是显示错误上网搜了一下说是登录数据库的客户端跟mysql8.0不兼容了,mysql8.0密码认证采用了新的密码格式[解决方法]打开命令管理器进入m......
  • React.js 网站开发:实现滚动加载动画
    React.js网站开发:实现滚动加载动画极客前端探索者前沿技术的探索者,编码艺术的实践者 最近在开发官网的过程中,涉及到UI动画的制作,其中滚动效果的使用比较频繁,特此整理一下,以便查询和温习。平滑向上过渡动画这种往下滚动过渡渐变显示的动画是最常......