canvas绘图

canvas绘图
寒霜1、canvas点连线生成图片
canvas点连线生成等值线就是将数组内索引为0的元素设置为起点,moveTo该点
其余点使用lineTo进行连接,当涉及到多维数组的时候,需要进入到数组的最内部循环进行上述操作
注意:如果需要绘制的图形铺满整个画布,则需要先计算出所有坐标点的极值点
,即最大最小的x、y,再结合画布width和height,算出每个方向上的缩放
倍率scaleX和scaleY,moveTo和lineTo的时候需要将缩放倍率计算进去
let xmin, xmax;
let ymin, ymax;
let xArr = [];
let yArr = [];
let scalex, scaley;
function drawChina() {
// 解析边界线
let obj = fs.readFileSync("./data/china.json");
let object = JSON.parse(obj);
let features = object.features;
// 计算极值
for (let i = 0; i < features.length; i++) {
let geo = features[i].geometry;
let coor = geo.coordinates;
for (let j = 0; j < coor.length; j++) {
for (let m = 0; m < coor[j].length; m++) {
for (let k = 0; k < coor[j][m].length; k++) {
xArr.push(coor[j][m][k][0]);
yArr.push(coor[j][m][k][1]);
}
}
}
}
let b = Array.from(new Set(xArr);
let num1, num2; //最小值与最大值
num1 = b[0];
num2 = b[1];
for (let i = 0; i < b.length - 1; i++) {
if (b[i] < num1) {
num1 = b[i];
}
if (b[i] > num2) {
num2 = b[i];
}
}
// console.log(num1, num2);
xmin = num1;
xmax = num2;
let a = Array.from(new Set(yArr);
let num3, num4; //最小值与最大值
num3 = a[0];
num4 = a[1];
for (let i = 0; i < a.length - 1; i++) {
if (a[i] < num3) {
num3 = a[i];
}
if (a[i] > num4) {
num4 = a[i];
}
}
ymin = num3;
ymax = num4;
// 打印极值
console.log(xmin, xmax, ymin, ymax);
// 循环绘制
for (let i = 0; i < features.length; i++) {
let feature = features[i];
let geometry = feature.geometry;
let type = geometry.type;
let coordinates = geometry.coordinates;
// 得到不同方向上的倍率
scalex = (xmax - xmin) / width;
scaley = (ymax - ymin) / height;
if (type === "Polygon") {
drawPolygon0(coordinates, scalex, scaley);
} else if (type === "MultiPolygon") {
for (let j = 0; j < coordinates.length; j++) {
let polygon = coordinates[j];
drawPolygon0(polygon, scalex, scaley);
}
}
}
// node环境保存图片,与绘制无关,可以不使用
function saveChina() {
const image = canvas.toDataURL("image/png");
const base64Data = image.replace(/^data:image\/\w+;base64,/, "");
const bufferData = new Buffer(base64Data, "base64");
fs.writeFile(
"./result/img/" + new Date().getTime() + ".png",
bufferData,
function (err) {
if (err) {
////console.log(err);
} else {
////console.log("图片保存成功");
}
}
);
}
// saveChina()
function drawPolygon0(polygon, scalex, scaley) {
ctx.beginPath();
let points = polygon[0];
for (let i = 0; i < points.length; i++) {
let point = points[i];
let x = Number(point[0]);
let y = Number(point[1]);
// 根据倍率移动点位置,进行满铺
// 由于canvas坐标系左上角为顶点,越往下y值越大,所以需要翻转
let l = (x - xmin) / scalex;
let t = height - (y - ymin) / scaley;
if (t == 0) {
// console.log("0");
}
if (i === 0) {
ctx.moveTo(l, t);
} else {
ctx.lineTo(l, t);
}
}
ctx.fillStyle = "rgba(255,255,255,1)";
ctx.strokeStyle = "rgba(255,0,0,1)";
ctx.lineWidth = 2;
ctx.closePath();
ctx.stroke();
ctx.fill();
}
}
2、canvas颜色的填充
canvas在绘制等值线(或者说是闭合区域的时候)往往需要在区域内填色,根据
globalCompositeOperation的不同属性值,会得到不同的结果,具体需要填色的情况
选择不同的属性值在有要求的情况下,可以根据闭合区域不同的高度值(假如是等高线)进行不同的填色
function drawOther() {
let colors = {
zuigaowen: [
{ Value_Min: 10, Value_Max: 15, color: "#F3BF48" },
{ Value_Min: 15, Value_Max: 20, color: "#FFAF41" },
{ Value_Min: 20, Value_Max: 25, color: "#F98E20" },
{ Value_Min: 25, Value_Max: 30, color: "#FF760A" },
{ Value_Min: 30, Value_Max: 35, color: "#EC5418" },
{ Value_Min: 35, Value_Max: 40, color: "#EE0D20" },
{ Value_Min: 40, Value_Max: 45, color: "#C70321" },
],
dibiaowendushikuang: [
{ Value_Min: 10, Value_Max: 20, color: "#A7DC32" },
{ Value_Min: 20, Value_Max: 30, color: "#F0C828" },
{ Value_Min: 30, Value_Max: 40, color: "#F09D29" },
{ Value_Min: 40, Value_Max: 50, color: "#F06429" },
{ Value_Min: 50, Value_Max: 60, color: "#DF3418" },
{ Value_Min: 60, Value_Max: 70, color: "#C20D0D" },
{ Value_Min: 70, Value_Max: 80, color: "#890323" },
],
bianwen: [
{ Value_Min: -6, Value_Max: -4, color: "#66A6FF" },
{ Value_Min: -4, Value_Max: -2, color: "#80CFFF" },
{ Value_Min: -2, Value_Max: 0, color: "#9AF9FF" },
{ Value_Min: 0, Value_Max: 2, color: "#FFFB87" },
{ Value_Min: 2, Value_Max: 4, color: "#FCDF76" },
{ Value_Min: 4, Value_Max: 6, color: "#F8BE5C" },
],
jiangshui: [
// { Value_Min: -10, Value_Max: 0.1, color: "rgba(0,0,0,0)" },
{ Value_Min: -10, Value_Max: 0.1, color: "rgba(0,0,0,0)" },
{ Value_Min: 0.1, Value_Max: 10, color: "#7DF0FF" },
{ Value_Min: 10, Value_Max: 50, color: "#0E3FD9" },
{ Value_Min: 50, Value_Max: 75, color: "#072586" },
],
fourteen: {
zhongshu: {
zs3: "#EB9700",
zs4: "#DC521E",
zs5: "#B00808",
},
},
};
let name = "./1.json";
fs.readFile(name, (err, result) => {
if (err) {
} else {
let object = JSON.parse(result);
let { CLOSED_CONTOURS } = object;
let arr = [];
let color = "";
let leixing = "fourteen";
let leixing_child = "zhongshu";
for (let i = 0; i < CLOSED_CONTOURS.length; i++) {
let CLOSED_CONTOURS_i = CLOSED_CONTOURS[i];
let { linePoint } = CLOSED_CONTOURS_i;
let { markName } = CLOSED_CONTOURS_i.lineMark;
console.log(colors[leixing][leixing_child][markName]);
arr.push(linePoint);
for (let m = 0; m < arr.length; m++) {
drawPolygon(
arr[m],
scalex,
scaley,
colors[leixing][leixing_child][markName]
);
}
}
const image = canvas.toDataURL("image/png");
const base64Data = image.replace(/^data:image\/\w+;base64,/, "");
const bufferData = new Buffer(base64Data, "base64");
fs.writeFile(
"./result/img/" + new Date().getTime() + ".png",
bufferData,
function (err) {
if (err) {
} else {
console.log("图片保存成功");
}
}
);
}
});
function drawPolygon(points, scalex, scaley, color) {
// console.log(scalex, scaley);
ctx.beginPath();
for (let i = 0; i < points.length; i++) {
let point = points[i];
// console.log(point);
let x = Number(point["lot"]);
let y = Number(point["lat"]);
// console.log(x, y);
let l = (x - xmin) / scalex;
let t = height - (y - ymin) / scaley;
// console.log(l, t);
if (i === 0) {
ctx.moveTo(l, t);
} else {
ctx.lineTo(l, t);
}
}
// ctx.globalCompositeOperation = "destination-over";
ctx.fillStyle = color;
ctx.strokeStyle = "#fff";
ctx.closePath();
ctx.stroke();
ctx.fill();
}
}
3、canvas图形形状的截取
canvas形状的截取,尤其是既包含填色,又包含截取(按另一个图形的形状截取)的情况下,
强烈建议分两步
3.1首先将目标canvas与源canvas生成图片,避免涂色相互影响的问题。
3.2然后将两张图片进行叠加截图
node环境为例
/**
* 测试canvas图像截取
* 先画元素图
* 再画中国地图
* 再截取
*/
const fs = require("fs");
const {createCanvas, loadImage} = require("canvas");
const width = 800;
const height = 800;
const canvas = createCanvas(width, height);
const ctx = canvas.getContext("2d");
//降雨使用的方法(或者说是没有铺满中国的元素图的截取方法)
function a(){
loadImage('./result/img/new/14/gaowen.png').then((yuansu) => {//加载元素图
console.log('yuansu',yuansu)
ctx.drawImage(yuansu, 0, 0, width, height)
loadImage('./result/img/china.png').then((china)=>{//加载中国地图
console.log('china',china)
ctx.globalCompositeOperation = "destination-in";
ctx.drawImage(china,0,0,width,height)
function result() {
const image = canvas.toDataURL("image/png");
const base64Data = image.replace(/^data:image\/\w+;base64,/, "");
const bufferData = new Buffer(base64Data, "base64");
fs.writeFile(
"./result/img/" + new Date().getTime() + ".png",
bufferData,
function (err) {
if (err) {
} else {
console.log("图片保存成功");
}
}
);
}
// setTimeout(function (){result()},1000)
result()
})
})
}
function b(){
loadImage('./result/img/china2.png').then((china) => {//加载中国地图
console.log('china',china)
ctx.drawImage(china, 0, 0, width, height)
loadImage('./result/img/new/14/gaowen.png').then((yuansu)=>{//加载元素图
console.log('yuansu',yuansu)
//todo:目前除去降雨数据,其他的都用下面的属性
// ctx.globalCompositeOperation = "source-atop"
//降雨时属性
// ctx.globalCompositeOperation = "destination-in";
ctx.drawImage(yuansu,0,0,width,height)
function result() {
const image = canvas.toDataURL("image/png");
const base64Data = image.replace(/^data:image\/\w+;base64,/, "");
const bufferData = new Buffer(base64Data, "base64");
fs.writeFile(
"./result/img/" + new Date().getTime() + ".png",
bufferData,
function (err) {
if (err) {
} else {
console.log("图片保存成功");
}
}
);
}
// setTimeout(function (){result()},1000)
result()
})
})
}
a()
// b()
总结:在绘制闭合区域(只有数组没有顺序)的时候,需要注意canvas涂色覆盖的问题,同时,需要截取复杂图形的时候,不建议直接在一个canvas对象内使用,可以使用两张图片叠加的形式。本文所使用的的环境是node,可以直接生成图片保存在本地,然后进行读取。可以猜想,利用不同变量保存每个canvas生成的base64也可以直接在浏览器生成类似效果,但本人未做尝试,只是猜想。另外,本文没有使用clip属性进行截图操作,因为没有思路。在颜色填充部分本文尝试使用save和restore属性,但是产生的结果与未使用时一致,如各位了解该如何使用上述属性或有兴趣技术沟通,可直接与我联系