山海鲸可视化——天地图画面和热力图

山海鲸引入天地图目前只有 iframe 的方式引入

首先我们创建一个文件夹
——index.html
——index.js
——data.js

大家都是大佬,我就不详细介绍了,上代码都能看得懂

首先是index.html

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta http-equiv="content-type" content="text/html; charset=utf-8"/>
  <title>天地图</title>
  <script type="text/javascript" src="http://api.tianditu.gov.cn/api?v=4.0&tk=天地图申请的key">
  </script>
  <style>
      body,
      html {
          width: 100%;
          height: 100%;
          margin: 0;
          font-family: "Microsoft YaHei",serif
      }

      #mapDiv {
          width: 100%;
          height: 100%;
      }

      input,
      b,
      p {
          margin-left: 5px;
          font-size: 14px
      }
  </style>

</head>
<script src="./index.js" defer></script>
<script src="./data.js" defer></script>

<body onload="onLoad()">
<div id="mapDiv">
</div>
</body>
</html>

index.html里面就是创建了基础的地图元素和引入方法 没啥东西

接下来是index.js

let map;
const zoom = 13; //缩放大小
let polygonInitArray = null; //保存默认地块数组
function onLoad() {
  //初始化地图对象
  map = new T.Map("mapDiv");
  //设置显示地图的中心点和级别
  map.centerAndZoom(new T.LngLat(102.85428, 24.82373), zoom);
  map.setMinZoom(12); // 设置最大缩放级别
  map.setStyle("indigo"); // 设置地图风格

  //允许鼠标滚轮缩放地图
  map.enableScrollWheelZoom();

  // 第一块
  const points1 = generatePolygonArray(lineData1);
  const polygon1 = createLandAndSetStyle(points1, "#fff", 2, 0.5, "#0BFA01", 1);

 // .....很多块
  polygonInitArray = [
    polygon1,
    //....很多块
  ];
  polygonInitArray.forEach((polygon) => map.addOverLay(polygon));

  // 生成地块数组函数
  function generatePolygonArray(data) {
    return data.map(({ lng, lat }) => new T.LngLat(lng, lat));
  }
  // 创建地块并为地块设置颜色的函数
  function createLandAndSetStyle(
    targetObj,
    color,
    weight,
    opacity,
    fillColor,
    fillOpacity,
    lineStyle = "solid"
  ) {
    return new T.Polygon(targetObj, {
      color,
      weight,
      opacity,
      fillColor,
      fillOpacity,
      lineStyle,
    });
  }
}

上面就是创建块的函数,还缺的就是组成这个块的每一个点的经纬度

接下来是data.js


const lineData1 = [
  { lng: 102.83233134, lat: 24.8511322 },
  { lng: 102.84071464, lat: 24.85366787 },
  { lng: 102.8415131, lat: 24.85049824 },
  { lng: 102.83971668, lat: 24.8482342 },
  { lng: 102.83742126, lat: 24.84705688 },
  { lng: 102.83402801, lat: 24.84841532 },
  { lng: 102.83233134, lat: 24.8511322 },
];


看不懂的直接复制代码,运行了就知道了,注意 天地图的key要自己申请

上面讲的是地图上勾画面

接下来我们说天地图热力图怎么画

首先也是创建文件夹

包含
——index.html
——index.js
——heatMap.js(这个是生成热力图关键的js不需要改动)
这里我没有把数据放data.js里面,全放index.js里面,懒了

首先index.html里面没啥改动,跟上面差不多

<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <meta http-equiv="content-type" content="text/html; charset=utf-8" />
    <title>天地图-地图API-范例-地图加载单图层</title>
    <script
      type="text/javascript"
      src="http://api.tianditu.gov.cn/api?v=4.0&tk=申请的天地图key"
    ></script>
    <style>
      body,
      html {
        width: 100%;
        height: 100%;
        margin: 0;
        font-family: "Microsoft YaHei", serif;
      }

      #mapDiv {
        width: 100%;
        height: 100%;
      }

      input,
      b,
      p {
        margin-left: 5px;
        font-size: 14px;
      }
    </style>
  </head>
  <script src="./index.js" defer></script>
  <script src="./heatMap.js" defer></script>

  <body onload="onLoad()">
    <div id="mapDiv"></div>
  </body>
</html>

接下来index.js

let map;
const zoom = 13;
function onLoad() {
  //初始化地图对象
  map = new T.Map("mapDiv");
  //设置显示地图的中心点和级别
  map.centerAndZoom(new T.LngLat(102.85428, 24.82373), zoom);
  map.setMinZoom(12); // 设置最大缩放级别
  map.setStyle("indigo"); // 设置地图风格

  //允许鼠标滚轮缩放地图
  map.enableScrollWheelZoom();

  // 创建一块热力图
  //参数说明如下:
  /* visible 热力图是否显示,默认为true
             * opacity 热力的透明度,1-100
             * radius 势力图的每个点的半径大小
             * gradient  {JSON} 热力图的渐变区间 . gradient如下所示
             *	{
             .2:'rgb(0, 255, 255)',
             .5:'rgb(0, 110, 255)',
             .8:'rgb(100, 0, 255)'
             }

             value 为颜色值.
             */
  const data = [
    { name: "test1", value: 190 },
    { name: "test2", value: 180 },
    { name: "test3", value: 170 },
    { name: "test4", value: 105 },
    { name: "test5", value: 95 },
    { name: "test6", value: 134 },
    { name: "test7", value: 167 },
    { name: "test8", value: 178 },
    { name: "test9", value: 87 },
    { name: "test10", value: 178 },
    { name: "test11", value: 147 },
    { name: "test12", value: 87 },
    { name: "test13", value: 100 },
    { name: "test14", value: 124 },
  ];
  const geoCoordMap = {
    test1: [102.85428, 24.82373],
    test2: [102.88428, 24.85373],
    test3: [102.89428, 24.83373],
    test4: [102.84303354, 24.80530684],
    test5: [102.84071464, 24.85366787],
    test6: [102.82823951, 24.85040768],
    test7: [102.83991634, 24.84270977],
    test8: [102.84370878, 24.84479278],
    test9: [102.84150136, 24.82757133],
    test10: [102.83916618, 24.8021391],
    test11: [102.88084795, 24.84942571],
    test12: [102.87013422, 24.81139329],
    test13: [102.88492484, 24.84942571],
    test14: [102.8701342, 24.81139329],
  };
  var convertData = function (data) {
    var res = [];
    for (var i = 0; i < data.length; i++) {
      var geoCoord = geoCoordMap[data[i].name];
      if (geoCoord) {
        res.push({
          name: data[i].name,
          lat: geoCoord[1],
          lng: geoCoord[0],
          count: data[i].value,
        });
      }
    }
    return res;
  };
  var points = convertData(data);
  let heatmapOverlay = new T.HeatmapOverlay({
    radius: 30,
    visible: true,
    gradient: {
      0.2: "#FAFFA8FF",
      0.5: "#FF8C12FF",
      0.8: "#F51D27FF",
    },
  });
  map.addOverLay(heatmapOverlay);
  heatmapOverlay.setDataSet({ data: points, max: 300 });
}

接下来就是一个heatMap.js文件,这个是我自己再天地图示例里面复制源码来的,下载要上github,懒得去,你们要有直接复制用

/**
 * 浠ョ壒娈婇珮浜殑褰㈠紡鐩磋灞曠ず鏁版嵁鍒嗗竷鐘跺喌銆�
 * 娉細chrome銆乻afari銆両E9鍙婁互涓婃祻瑙堝櫒锛屾牳蹇冪殑浠g爜涓昏鏉ヨ嚜浜庣涓夋柟heatmap.js銆侤author juyang
 */
T.HeatmapOverlay = T.Overlay.extend({
  /**
   *鏋勯€�
   * @param options
   */
  initialize: function (options) {
    this.conf = options;
    this.heatmap = null;
    this.latlngs = [];
    this.bounds = null;
  },

  onAdd: function (map) {
    this._map = map;
    var el = document.createElement("div");
    el.style.position = "absolute";
    el.style.top = 0;
    el.style.left = 0;
    el.style.border = 0;
    el.style.width = this._map.getSize().x + "px";
    el.style.height = this._map.getSize().y + "px";

    this.conf.container = el;
    if (!this.isSupportCanvas()) {
      //鍒ゆ柇鏄惁鏀寔Canvas.
      return el;
    }
    this.conf.valueField = this.conf.valueField || "count";
    this.conf.latField = this.conf.latField || "lat";
    this.conf.lngField = this.conf.lngField || "lng";
    this.heatmap = h337.create(this.conf);
    this.heatmap._renderer.setDimensions(
      this._map.getSize().x,
      this._map.getSize().y
    );
    map.getPanes().overlayPane.appendChild(el);
    map.on("moveend", this._reset, this);
    this._div = el;
  },

  onRemove: function (map) {
    map.getPanes().overlayPane.removeChild(this._div);
    map.off("moveend", this._reset, this);
  },
  _reset: function () {
    var size = this._map.getSize();
    this._div.style.width = size.x + "px";
    this._div.style.height = size.y + "px";
    this.heatmap._renderer.setDimensions(size.x, size.y);
    this.draw();
  },
  getElement: function () {
    return this.conf.container;
  },

  draw: function () {
    if (!this.isSupportCanvas()) {
      //鍒ゆ柇鏄惁鏀寔Canvas.
      return;
    }
    //  if (this.isHidden())  return;
    var currentBounds = this._map.getBounds();
    if (!this.isadd && currentBounds.equals(this.bounds)) {
      this.isadd = false;
      return;
    }
    this.bounds = currentBounds;

    var ne = this._map.lngLatToLayerPoint(currentBounds.getNorthEast()),
      sw = this._map.lngLatToLayerPoint(currentBounds.getSouthWest()),
      topY = ne.y,
      leftX = sw.x,
      h = sw.y - ne.y,
      w = ne.x - sw.x;

    this.conf.container.style.width = w + "px";
    this.conf.container.style.height = h + "px";
    this.conf.container.style[this.CSS_TRANSFORM()] =
      "translate(" + Math.round(leftX) + "px," + Math.round(topY) + "px)";

    if (this.latlngs.length > 0) {
      this.heatmap.removeData();
    }
    var len = this.latlngs.length;
    d = {
      max: this.heatmap._store.getData().max,
      data: [],
    };

    while (len--) {
      var latlng = this.latlngs[len].latlng;
      if (!currentBounds.contains(latlng)) {
        continue;
      }

      var roundedPoint = this._getContainerPoint(latlng);
      d.data.push({
        x: roundedPoint.x,
        y: roundedPoint.y,
        count: this.latlngs[len].c,
      });
    }
    this.heatmap.setData(d);
  },

  CSS_TRANSFORM: function () {
    var div = document.createElement("div");
    var props = [
      "transform",
      "WebkitTransform",
      "MozTransform",
      "OTransform",
      "msTransform",
    ];

    for (var i = 0; i < props.length; i++) {
      var prop = props[i];
      if (div.style[prop] !== undefined) {
        return prop;
      }
    }
    return props[0];
  },
  /**
   * 璁剧疆鐑姏鍥惧睍鐜扮殑璇︾粏鏁版嵁, 瀹炵幇涔嬪悗,鍗冲彲浠ョ珛鍒诲睍鐜�
   * {"<b>max</b>" : {Number} 鏉冮噸鐨勬渶澶у€�,
   * <br />"<b>data</b>" : {Array} 鍧愭爣璇︾粏鏁版嵁,鏍煎紡濡備笅 <br/>
   * {"lng":116.421969,"lat":39.913527,"count":3}, 鍏朵腑<br/>
   * lng lat鍒嗗埆涓虹粡绾害, count鏉冮噸鍊�
   * @param data
   */
  setDataSet: function (data) {
    this.data = data;
    if (!this.isSupportCanvas()) {
      //鍒ゆ柇鏄惁鏀寔Canvas.
      return;
    }
    var currentBounds = this._map.getBounds();
    var mapdata = {
      max: data.max,
      data: [],
    };
    var d = data.data,
      dlen = d.length;

    this.latlngs = [];
    this.heatmap.removeData();

    while (dlen--) {
      var latlng = new T.LngLat(
        d[dlen][this.conf.lngField],
        d[dlen][this.conf.latField]
      );
      this.latlngs.push({
        latlng: latlng,
        c: d[dlen].count,
      });

      if (!currentBounds.contains(latlng)) {
        continue;
      }

      var point = this._getContainerPoint(latlng);

      mapdata.data.push({
        x: point.x,
        y: point.y,
        count: d[dlen].count,
      });
    }

    this.heatmap.setData(mapdata);
  },

  _getContainerPoint: function (latlng) {
    var currentBounds = this._map.getBounds();
    var divPixel = this._map.lngLatToLayerPoint(latlng),
      leftX = this._map.lngLatToLayerPoint(currentBounds.getSouthWest()).x,
      topY = this._map.lngLatToLayerPoint(currentBounds.getNorthEast()).y,
      screenPixel = new T.Point(divPixel.x - leftX, divPixel.y - topY);
    var point = this.pixelTransform(screenPixel);

    return point;
  },

  /**
   * 娣诲姞鍔垮姏鍥剧殑璇︾粏鍧愭爣鐐�
   * @param {Number} lng 缁忓害鍧愭爣
   * @param {Number} lat 缁忓害鍧愭爣
   * @param {Number} count 缁忓害鍧愭爣
   */
  addDataPoint: function (lng, lat, count) {
    if (!this.isSupportCanvas()) {
      return;
    }
    if (this.data && this.data.data) {
      this.data.data.push({
        lng: lng,
        lat: lat,
        count: count,
      });
    }
    var latlng = new T.LngLat(lng, lat),
      point = this.pixelTransform(this._map.lngLatToContainerPoint(latlng));

    this.latlngs.push({
      latlng: latlng,
      c: count,
    });

    // this.heatmap.addData({x: point.x, y: point.y, value: count });
    this.isadd = true;
    this.draw();
  },
  /**
   * 鍐呴儴浣跨敤鐨勫潗鏍囪浆鍖�
   * @param p
   * @returns {*}
   */
  pixelTransform: function (p) {
    var h = this.heatmap._config.container.clientHeight,
      w = this.heatmap._config.container.clientWidth;
    if (w == 0 || h == 0) return p;
    while (p.x < 0) {
      p.x += w;
    }

    while (p.x > w) {
      p.x -= w;
    }

    while (p.y < 0) {
      p.y += h;
    }

    while (p.y > h) {
      p.y -= h;
    }

    p.x = p.x >> 0;
    p.y = p.y >> 0;

    return p;
  },
  /**
   * 鏇存敼鐑姏鍥剧殑灞曠幇鎴栬€呭叧闂�
   */

  toggle: function () {
    if (!this.isSupportCanvas()) {
      //鍒ゆ柇鏄惁鏀寔Canvas.
      return;
    }
    if (this.conf.visible === true) {
      this.conf.visible = false;
    } else {
      this.conf.visible = true;
    }
    if (this.conf.visible) {
      this.conf.container.style.display = "block";
    } else {
      this.conf.container.style.display = "none";
    }
    return this.conf.visible;
  },

  setOptions: function (options) {
    if (!this.isSupportCanvas()) {
      //鍒ゆ柇鏄惁鏀寔Canvas.
      return;
    }

    for (var key in options) {
      if (key == "radius") {
        this.heatmap._store._cfgRadius = options[key];
      }
      if (key == "opacity") {
        options[key] = options[key] / 100;
      }
    }
    this.heatmap.configure(options);
    if (this.data) {
      this.setDataSet(this.data); //閲嶆柊娓叉煋
    }
  },
  isSupportCanvas: function () {
    var elem = document.createElement("canvas");
    return !!(elem.getContext && elem.getContext("2d"));
  },
});
/*==============================浠ヤ笂閮ㄥ垎涓轰笓涓哄ぉ鍦板浘鎵撻€犵殑瑕嗙洊鐗�===================================================*/
/*==============================浠ヤ笅閮ㄥ垎涓篽eatmap.js鐨勬牳蹇冧唬鐮�,鍙礋璐g儹鍔涘浘鐨勫睍鐜�====================================*/

/*
 * heatmap.js v2.0.2 | JavaScript Heatmap Library
 *
 * Copyright 2008-2016 Patrick Wied <heatmapjs@patrick-wied.at> - All rights reserved.
 * Dual licensed under MIT and Beerware license
 *
 * :: 2016-02-04 21:41
 */
(function (name, context, factory) {
  // Supports UMD. AMD, CommonJS/Node.js and browser context
  if (typeof module !== "undefined" && module.exports) {
    module.exports = factory();
  } else if (typeof define === "function" && define.amd) {
    define(factory);
  } else {
    context[name] = factory();
  }
})("h337", this, function () {
  // Heatmap Config stores default values and will be merged with instance config
  var HeatmapConfig = {
    defaultRadius: 40,
    defaultRenderer: "canvas2d",
    defaultGradient: {
      0.25: "rgb(0,0,255)",
      0.55: "rgb(0,255,0)",
      0.85: "yellow",
      1.0: "rgb(255,0,0)",
    },
    defaultMaxOpacity: 1,
    defaultMinOpacity: 0,
    defaultBlur: 0.85,
    defaultXField: "x",
    defaultYField: "y",
    defaultValueField: "value",
    plugins: {},
  };
  var Store = (function StoreClosure() {
    var Store = function Store(config) {
      this._coordinator = {};
      this._data = [];
      this._radi = [];
      this._min = 0;
      this._max = 1;
      this._xField = config["xField"] || config.defaultXField;
      this._yField = config["yField"] || config.defaultYField;
      this._valueField = config["valueField"] || config.defaultValueField;

      if (config["radius"]) {
        this._cfgRadius = config["radius"];
      }
    };

    var defaultRadius = HeatmapConfig.defaultRadius;

    Store.prototype = {
      // when forceRender = false -> called from setData, omits renderall event
      _organiseData: function (dataPoint, forceRender) {
        var x = dataPoint[this._xField];
        var y = dataPoint[this._yField];
        var radi = this._radi;
        var store = this._data;
        var max = this._max;
        var min = this._min;
        var value = dataPoint[this._valueField] || 1;
        var radius = dataPoint.radius || this._cfgRadius || defaultRadius;

        if (!store[x]) {
          store[x] = [];
          radi[x] = [];
        }

        if (!store[x][y]) {
          store[x][y] = value;
          radi[x][y] = radius;
        } else {
          store[x][y] += value;
        }

        if (store[x][y] > max) {
          if (!forceRender) {
            this._max = store[x][y];
          } else {
            this.setDataMax(store[x][y]);
          }
          return false;
        } else {
          return {
            x: x,
            y: y,
            value: value,
            radius: radius,
            min: min,
            max: max,
          };
        }
      },
      _unOrganizeData: function () {
        var unorganizedData = [];
        var data = this._data;
        var radi = this._radi;

        for (var x in data) {
          for (var y in data[x]) {
            unorganizedData.push({
              x: x,
              y: y,
              radius: radi[x][y],
              value: data[x][y],
            });
          }
        }
        return {
          min: this._min,
          max: this._max,
          data: unorganizedData,
        };
      },
      _onExtremaChange: function () {
        this._coordinator.emit("extremachange", {
          min: this._min,
          max: this._max,
        });
      },
      addData: function () {
        if (arguments[0].length > 0) {
          var dataArr = arguments[0];
          var dataLen = dataArr.length;
          while (dataLen--) {
            this.addData.call(this, dataArr[dataLen]);
          }
        } else {
          // add to store
          var organisedEntry = this._organiseData(arguments[0], true);
          if (organisedEntry) {
            this._coordinator.emit("renderpartial", {
              min: this._min,
              max: this._max,
              data: [organisedEntry],
            });
          }
        }
        return this;
      },
      setData: function (data) {
        var dataPoints = data.data;
        var pointsLen = dataPoints.length;

        // reset data arrays
        this._data = [];
        this._radi = [];

        for (var i = 0; i < pointsLen; i++) {
          this._organiseData(dataPoints[i], false);
        }
        this._max = data.max;
        this._min = data.min || 0;

        this._onExtremaChange();
        this._coordinator.emit("renderall", this._getInternalData());
        return this;
      },
      removeData: function () {
        // TODO: implement
      },
      setDataMax: function (max) {
        this._max = max;
        this._onExtremaChange();
        this._coordinator.emit("renderall", this._getInternalData());
        return this;
      },
      setDataMin: function (min) {
        this._min = min;
        this._onExtremaChange();
        this._coordinator.emit("renderall", this._getInternalData());
        return this;
      },
      setCoordinator: function (coordinator) {
        this._coordinator = coordinator;
      },
      _getInternalData: function () {
        return {
          max: this._max,
          min: this._min,
          data: this._data,
          radi: this._radi,
        };
      },
      getData: function () {
        return this._unOrganizeData();
      } /*,

           TODO: rethink.

           getValueAt: function(point) {
           var value;
           var radius = 100;
           var x = point.x;
           var y = point.y;
           var data = this._data;

           if (data[x] && data[x][y]) {
           return data[x][y];
           } else {
           var values = [];
           // radial search for datapoints based on default radius
           for(var distance = 1; distance < radius; distance++) {
           var neighbors = distance * 2 +1;
           var startX = x - distance;
           var startY = y - distance;

           for(var i = 0; i < neighbors; i++) {
           for (var o = 0; o < neighbors; o++) {
           if ((i == 0 || i == neighbors-1) || (o == 0 || o == neighbors-1)) {
           if (data[startY+i] && data[startY+i][startX+o]) {
           values.push(data[startY+i][startX+o]);
           }
           } else {
           continue;
           }
           }
           }
           }
           if (values.length > 0) {
           return Math.max.apply(Math, values);
           }
           }
           return false;
           }*/,
    };

    return Store;
  })();

  var Canvas2dRenderer = (function Canvas2dRendererClosure() {
    var _getColorPalette = function (config) {
      var gradientConfig = config.gradient || config.defaultGradient;
      var paletteCanvas = document.createElement("canvas");
      var paletteCtx = paletteCanvas.getContext("2d");

      paletteCanvas.width = 256;
      paletteCanvas.height = 1;

      var gradient = paletteCtx.createLinearGradient(0, 0, 256, 1);
      for (var key in gradientConfig) {
        gradient.addColorStop(key, gradientConfig[key]);
      }

      paletteCtx.fillStyle = gradient;
      paletteCtx.fillRect(0, 0, 256, 1);

      return paletteCtx.getImageData(0, 0, 256, 1).data;
    };

    var _getPointTemplate = function (radius, blurFactor) {
      var tplCanvas = document.createElement("canvas");
      var tplCtx = tplCanvas.getContext("2d");
      var x = radius;
      var y = radius;
      tplCanvas.width = tplCanvas.height = radius * 2;

      if (blurFactor == 1) {
        tplCtx.beginPath();
        tplCtx.arc(x, y, radius, 0, 2 * Math.PI, false);
        tplCtx.fillStyle = "rgba(0,0,0,1)";
        tplCtx.fill();
      } else {
        var gradient = tplCtx.createRadialGradient(
          x,
          y,
          radius * blurFactor,
          x,
          y,
          radius
        );
        gradient.addColorStop(0, "rgba(0,0,0,1)");
        gradient.addColorStop(1, "rgba(0,0,0,0)");
        tplCtx.fillStyle = gradient;
        tplCtx.fillRect(0, 0, 2 * radius, 2 * radius);
      }

      return tplCanvas;
    };

    var _prepareData = function (data) {
      var renderData = [];
      var min = data.min;
      var max = data.max;
      var radi = data.radi;
      var data = data.data;

      var xValues = Object.keys(data);
      var xValuesLen = xValues.length;

      while (xValuesLen--) {
        var xValue = xValues[xValuesLen];
        var yValues = Object.keys(data[xValue]);
        var yValuesLen = yValues.length;
        while (yValuesLen--) {
          var yValue = yValues[yValuesLen];
          var value = data[xValue][yValue];
          var radius = radi[xValue][yValue];
          renderData.push({
            x: xValue,
            y: yValue,
            value: value,
            radius: radius,
          });
        }
      }

      return {
        min: min,
        max: max,
        data: renderData,
      };
    };

    function Canvas2dRenderer(config) {
      var container = config.container;
      var shadowCanvas = (this.shadowCanvas = document.createElement("canvas"));
      var canvas = (this.canvas =
        config.canvas || document.createElement("canvas"));
      var renderBoundaries = (this._renderBoundaries = [10000, 10000, 0, 0]);

      var computed = getComputedStyle(config.container) || {};

      canvas.className = "heatmap-canvas";

      this._width =
        canvas.width =
        shadowCanvas.width =
          config.width || +computed.width.replace(/px/, "");
      this._height =
        canvas.height =
        shadowCanvas.height =
          config.height || +computed.height.replace(/px/, "");

      this.shadowCtx = shadowCanvas.getContext("2d");
      this.ctx = canvas.getContext("2d");

      // @TODO:
      // conditional wrapper

      canvas.style.cssText = shadowCanvas.style.cssText =
        "position:absolute;left:0;top:0;";

      container.style.position = "relative";
      container.appendChild(canvas);

      this._palette = _getColorPalette(config);
      this._templates = {};

      this._setStyles(config);
    }

    Canvas2dRenderer.prototype = {
      renderPartial: function (data) {
        if (data.data.length > 0) {
          this._drawAlpha(data);
          this._colorize();
        }
      },
      renderAll: function (data) {
        // reset render boundaries
        this._clear();
        if (data.data.length > 0) {
          this._drawAlpha(_prepareData(data));
          this._colorize();
        }
      },
      _updateGradient: function (config) {
        this._palette = _getColorPalette(config);
      },
      updateConfig: function (config) {
        if (config["gradient"]) {
          this._updateGradient(config);
        }
        this._setStyles(config);
      },
      setDimensions: function (width, height) {
        this._width = width;
        this._height = height;
        this.canvas.width = this.shadowCanvas.width = width;
        this.canvas.height = this.shadowCanvas.height = height;
      },
      _clear: function () {
        this.shadowCtx.clearRect(0, 0, this._width, this._height);
        this.ctx.clearRect(0, 0, this._width, this._height);
      },
      _setStyles: function (config) {
        this._blur = config.blur == 0 ? 0 : config.blur || config.defaultBlur;

        if (config.backgroundColor) {
          this.canvas.style.backgroundColor = config.backgroundColor;
        }

        this._width =
          this.canvas.width =
          this.shadowCanvas.width =
            config.width || this._width;
        this._height =
          this.canvas.height =
          this.shadowCanvas.height =
            config.height || this._height;

        this._opacity = (config.opacity || 0) * 255;
        this._maxOpacity =
          (config.maxOpacity || config.defaultMaxOpacity) * 255;
        this._minOpacity =
          (config.minOpacity || config.defaultMinOpacity) * 255;
        this._useGradientOpacity = !!config.useGradientOpacity;
      },
      _drawAlpha: function (data) {
        var min = (this._min = data.min);
        var max = (this._max = data.max);
        var data = data.data || [];
        var dataLen = data.length;
        // on a point basis?
        var blur = 1 - this._blur;

        while (dataLen--) {
          var point = data[dataLen];

          var x = point.x;
          var y = point.y;
          var radius = point.radius;
          // if value is bigger than max
          // use max as value
          var value = Math.min(point.value, max);
          var rectX = x - radius;
          var rectY = y - radius;
          var shadowCtx = this.shadowCtx;

          var tpl;
          if (!this._templates[radius]) {
            this._templates[radius] = tpl = _getPointTemplate(radius, blur);
          } else {
            tpl = this._templates[radius];
          }
          // value from minimum / value range
          // => [0, 1]
          var templateAlpha = (value - min) / (max - min);
          // this fixes #176: small values are not visible because globalAlpha < .01 cannot be read from imageData
          shadowCtx.globalAlpha = templateAlpha < 0.01 ? 0.01 : templateAlpha;

          shadowCtx.drawImage(tpl, rectX, rectY);

          // update renderBoundaries
          if (rectX < this._renderBoundaries[0]) {
            this._renderBoundaries[0] = rectX;
          }
          if (rectY < this._renderBoundaries[1]) {
            this._renderBoundaries[1] = rectY;
          }
          if (rectX + 2 * radius > this._renderBoundaries[2]) {
            this._renderBoundaries[2] = rectX + 2 * radius;
          }
          if (rectY + 2 * radius > this._renderBoundaries[3]) {
            this._renderBoundaries[3] = rectY + 2 * radius;
          }
        }
      },
      _colorize: function () {
        var x = this._renderBoundaries[0];
        var y = this._renderBoundaries[1];
        var width = this._renderBoundaries[2] - x;
        var height = this._renderBoundaries[3] - y;
        var maxWidth = this._width;
        var maxHeight = this._height;
        var opacity = this._opacity;
        var maxOpacity = this._maxOpacity;
        var minOpacity = this._minOpacity;
        var useGradientOpacity = this._useGradientOpacity;

        if (x < 0) {
          x = 0;
        }
        if (y < 0) {
          y = 0;
        }
        if (x + width > maxWidth) {
          width = maxWidth - x;
        }
        if (y + height > maxHeight) {
          height = maxHeight - y;
        }

        var img = this.shadowCtx.getImageData(x, y, width, height);
        var imgData = img.data;
        var len = imgData.length;
        var palette = this._palette;

        for (var i = 3; i < len; i += 4) {
          var alpha = imgData[i];
          var offset = alpha * 4;

          if (!offset) {
            continue;
          }

          var finalAlpha;
          if (opacity > 0) {
            finalAlpha = opacity;
          } else {
            if (alpha < maxOpacity) {
              if (alpha < minOpacity) {
                finalAlpha = minOpacity;
              } else {
                finalAlpha = alpha;
              }
            } else {
              finalAlpha = maxOpacity;
            }
          }

          imgData[i - 3] = palette[offset];
          imgData[i - 2] = palette[offset + 1];
          imgData[i - 1] = palette[offset + 2];
          imgData[i] = useGradientOpacity ? palette[offset + 3] : finalAlpha;
        }

        img.data = imgData;
        this.ctx.putImageData(img, x, y);

        this._renderBoundaries = [1000, 1000, 0, 0];
      },
      getValueAt: function (point) {
        var value;
        var shadowCtx = this.shadowCtx;
        var img = shadowCtx.getImageData(point.x, point.y, 1, 1);
        var data = img.data[3];
        var max = this._max;
        var min = this._min;

        value = (Math.abs(max - min) * (data / 255)) >> 0;

        return value;
      },
      getDataURL: function () {
        return this.canvas.toDataURL();
      },
    };

    return Canvas2dRenderer;
  })();

  var Renderer = (function RendererClosure() {
    var rendererFn = false;

    if (HeatmapConfig["defaultRenderer"] === "canvas2d") {
      rendererFn = Canvas2dRenderer;
    }

    return rendererFn;
  })();

  var Util = {
    merge: function () {
      var merged = {};
      var argsLen = arguments.length;
      for (var i = 0; i < argsLen; i++) {
        var obj = arguments[i];
        for (var key in obj) {
          merged[key] = obj[key];
        }
      }
      return merged;
    },
  };
  // Heatmap Constructor
  var Heatmap = (function HeatmapClosure() {
    var Coordinator = (function CoordinatorClosure() {
      function Coordinator() {
        this.cStore = {};
      }

      Coordinator.prototype = {
        on: function (evtName, callback, scope) {
          var cStore = this.cStore;

          if (!cStore[evtName]) {
            cStore[evtName] = [];
          }
          cStore[evtName].push(function (data) {
            return callback.call(scope, data);
          });
        },
        emit: function (evtName, data) {
          var cStore = this.cStore;
          if (cStore[evtName]) {
            var len = cStore[evtName].length;
            for (var i = 0; i < len; i++) {
              var callback = cStore[evtName][i];
              callback(data);
            }
          }
        },
      };

      return Coordinator;
    })();

    var _connect = function (scope) {
      var renderer = scope._renderer;
      var coordinator = scope._coordinator;
      var store = scope._store;

      coordinator.on("renderpartial", renderer.renderPartial, renderer);
      coordinator.on("renderall", renderer.renderAll, renderer);
      coordinator.on("extremachange", function (data) {
        scope._config.onExtremaChange &&
          scope._config.onExtremaChange({
            min: data.min,
            max: data.max,
            gradient:
              scope._config["gradient"] || scope._config["defaultGradient"],
          });
      });
      store.setCoordinator(coordinator);
    };

    function Heatmap() {
      var config = (this._config = Util.merge(
        HeatmapConfig,
        arguments[0] || {}
      ));
      this._coordinator = new Coordinator();
      if (config["plugin"]) {
        var pluginToLoad = config["plugin"];
        if (!HeatmapConfig.plugins[pluginToLoad]) {
          throw new Error(
            "Plugin '" +
              pluginToLoad +
              "' not found. Maybe it was not registered."
          );
        } else {
          var plugin = HeatmapConfig.plugins[pluginToLoad];
          // set plugin renderer and store
          this._renderer = new plugin.renderer(config);
          this._store = new plugin.store(config);
        }
      } else {
        this._renderer = new Renderer(config);
        this._store = new Store(config);
      }
      _connect(this);
    }

    // @TODO:
    // add API documentation
    Heatmap.prototype = {
      addData: function () {
        this._store.addData.apply(this._store, arguments);
        return this;
      },
      removeData: function () {
        this._store.removeData &&
          this._store.removeData.apply(this._store, arguments);
        return this;
      },
      setData: function () {
        this._store.setData.apply(this._store, arguments);
        return this;
      },
      setDataMax: function () {
        this._store.setDataMax.apply(this._store, arguments);
        return this;
      },
      setDataMin: function () {
        this._store.setDataMin.apply(this._store, arguments);
        return this;
      },
      configure: function (config) {
        this._config = Util.merge(this._config, config);
        this._renderer.updateConfig(this._config);
        this._coordinator.emit("renderall", this._store._getInternalData());
        return this;
      },
      repaint: function () {
        this._coordinator.emit("renderall", this._store._getInternalData());
        return this;
      },
      getData: function () {
        return this._store.getData();
      },
      getDataURL: function () {
        return this._renderer.getDataURL();
      },
      getValueAt: function (point) {
        if (this._store.getValueAt) {
          return this._store.getValueAt(point);
        } else if (this._renderer.getValueAt) {
          return this._renderer.getValueAt(point);
        } else {
          return null;
        }
      },
    };

    return Heatmap;
  })();

  // core
  var heatmapFactory = {
    create: function (config) {
      return new Heatmap(config);
    },
    register: function (pluginKey, plugin) {
      HeatmapConfig.plugins[pluginKey] = plugin;
    },
  };

  return heatmapFactory;
});

  • 9
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值