@visactor/vgrammar-wordcloud-shape
Version:
Layout WordCloud in specified shape, this is a transform for VGrammar.
159 lines (154 loc) • 8.69 kB
JavaScript
;
function segmentation(segmentationInput) {
const {size: size, maskCanvas: maskCanvas} = segmentationInput, imageData = maskCanvas.getContext("2d", {
willReadFrequently: !0
}).getImageData(0, 0, maskCanvas.width, maskCanvas.height), labels = new Array(size[0] * size[1]).fill(0);
let curLabel = 1;
const offset = [ [ 0, 1 ], [ 1, 0 ], [ -1, 0 ], [ 0, -1 ] ];
let queue = [];
for (let i = 0; i < size[1]; i++) for (let j = 0; j < size[0]; j++) if (0 === labels[i * size[0] + j] && !segmentationInput.isEmptyPixel(imageData, i, j)) {
labels[i * size[0] + j] = curLabel, queue.push([ i, j ]);
for (let k = 0; k < queue.length; k++) for (let m = 0; m < 4; m++) {
let row = queue[k][0] + offset[m][0], col = queue[k][1] + offset[m][1];
row = row < 0 ? 0 : row >= size[1] ? size[1] - 1 : row, col = col < 0 ? 0 : col >= size[0] ? size[0] - 1 : col,
0 !== labels[row * size[0] + col] || segmentationInput.isEmptyPixel(imageData, row, col) || (labels[row * size[0] + col] = curLabel,
queue.push([ row, col ]));
}
curLabel++, queue = [];
}
const boundaries = {}, areas = {}, centers = {}, maxPoints = {}, maxR = {}, ratios = {}, shapeBounds = {
x1: 1 / 0,
x2: -1 / 0,
y1: 1 / 0,
y2: -1 / 0,
width: 0,
height: 0
};
for (let i = 0; i < size[1]; i++) for (let j = 0; j < size[0]; j++) {
const label = labels[i * size[0] + j];
0 !== label && (isBoundaryPixel(i, j) && (boundaries[label] = boundaries[label] || [],
boundaries[label].push([ j, i ]), maxPoints[label] || (maxPoints[label] = [ 1 / 0, -1 / 0, 1 / 0, -1 / 0 ]),
i < maxPoints[label][0] && (maxPoints[label][0] = i), i > maxPoints[label][1] && (maxPoints[label][1] = i),
j < maxPoints[label][2] && (maxPoints[label][2] = j), j > maxPoints[label][3] && (maxPoints[label][3] = j),
j < shapeBounds.x1 && (shapeBounds.x1 = j), j > shapeBounds.x2 && (shapeBounds.x2 = j),
i < shapeBounds.y1 && (shapeBounds.y1 = i), i > shapeBounds.y2 && (shapeBounds.y2 = i)),
areas[label] = areas[label] || 0, areas[label]++);
}
const allBoundaries = [];
for (const label in boundaries) {
const boundary = boundaries[label], x = ~~(boundary.reduce(((acc, cur) => acc + cur[0]), 0) / boundary.length), y = ~~(boundary.reduce(((acc, cur) => acc + cur[1]), 0) / boundary.length);
centers[label] = [ x, y ], allBoundaries.push(...boundary);
const [yMin, yMax, xMin, xMax] = maxPoints[label];
maxR[label] = ~~Math.max(Math.sqrt((x - xMin) ** 2 + (y - yMin) ** 2), Math.sqrt((x - xMax) ** 2 + (y - yMax) ** 2), Math.sqrt((x - xMin) ** 2 + (y - yMax) ** 2), Math.sqrt((x - xMax) ** 2 + (y - yMin) ** 2)),
ratios[label] = (xMax - xMin) / (yMax - yMin);
}
const regions = Object.keys(centers).map((key => ({
label: key - 1,
boundary: boundaries[key],
area: areas[key],
center: centers[key],
maxPoint: maxPoints[key],
maxR: maxR[key],
ratio: ratios[key]
})));
shapeBounds.width = shapeBounds.x2 - shapeBounds.x1 + 1, shapeBounds.height = shapeBounds.y2 - shapeBounds.y1 + 1;
const x = ~~(allBoundaries.reduce(((acc, cur) => acc + cur[0]), 0) / allBoundaries.length), y = ~~(allBoundaries.reduce(((acc, cur) => acc + cur[1]), 0) / allBoundaries.length), shapeMaxR = ~~Math.max(Math.sqrt((x - shapeBounds.x1) ** 2 + (y - shapeBounds.y1) ** 2), Math.sqrt((x - shapeBounds.x2) ** 2 + (y - shapeBounds.y2) ** 2), Math.sqrt((x - shapeBounds.x1) ** 2 + (y - shapeBounds.y2) ** 2), Math.sqrt((x - shapeBounds.x2) ** 2 + (y - shapeBounds.y1) ** 2)), shapeRatio = shapeBounds.width / shapeBounds.height, shapeArea = Object.keys(areas).reduce(((acc, key) => acc + areas[key]), 0), segmentation = {
regions: regions,
labels: labels,
labelNumber: curLabel - 1
};
return Object.assign(segmentationInput, {
segmentation: segmentation,
shapeBounds: shapeBounds,
shapeMaxR: shapeMaxR,
shapeRatio: shapeRatio,
shapeCenter: [ x, y ],
shapeArea: shapeArea
});
function isBoundaryPixel(i, j) {
const offset = [ [ 0, 1 ], [ 1, 0 ], [ -1, 0 ], [ 0, -1 ] ];
if (0 === i || 0 === j || i === size[1] - 1 || j === size[0] - 1) return !0;
for (let k = 0; k < 4; k++) {
let row = i + offset[k][0], col = j + offset[k][1];
if (row = row < 0 ? 0 : row >= size[1] ? size[1] - 1 : row, col = col < 0 ? 0 : col >= size[0] ? size[0] - 1 : col,
0 === labels[row * size[0] + col]) return !0;
}
return !1;
}
}
function removeBorder(image, canvas, isEmptyPixel) {
canvas.width = image.width, canvas.height = image.height;
const ctx = canvas.getContext("2d", {
willReadFrequently: !0
});
ctx.clearRect(0, 0, canvas.width, canvas.height), ctx.drawImage(image, 0, 0);
const width = canvas.width, imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
let top = 0, bottom = imageData.height, left = 0, right = imageData.width;
const rowBlank = (width, y) => {
for (let x = 0; x < width; ++x) if (!isEmptyPixel(imageData, y, x)) return !1;
return !0;
}, columnBlank = (x, y0, y1) => {
for (let y = y0; y < y1; ++y) if (!isEmptyPixel(imageData, y, x)) return !1;
return !0;
};
for (;top < bottom && rowBlank(width, top); ) ++top;
for (;bottom - 1 > top && rowBlank(width, bottom - 1); ) --bottom;
for (;left < right && columnBlank(left, top, bottom); ) ++left;
for (;right - 1 > left && columnBlank(right - 1, top, bottom); ) --right;
const trimmed = ctx.getImageData(left, top, right - left, bottom - top);
return canvas.width = trimmed.width, canvas.height = trimmed.height, ctx.clearRect(0, 0, canvas.width, canvas.height),
ctx.putImageData(trimmed, 0, 0), canvas;
}
function scaleAndMiddleShape(image, size) {
const width = image.width, height = image.height;
let scale = size[0] / width;
height * scale > size[1] && (scale = size[1] / height);
const newWidth = Math.floor(scale * width), newHeight = Math.floor(scale * height);
return {
x: (size[0] - newWidth) / 2,
y: (size[1] - newHeight) / 2,
width: newWidth,
height: newHeight,
scale: scale
};
}
function allocateWords(words, segmentationOutput) {
const {segmentation: {regions: regions}} = segmentationOutput;
let areaMax = -1 / 0, totalArea = 0, areaMaxIndex = 0;
regions.forEach(((region, index) => {
const area = region.area;
area > areaMax && (areaMax = area, areaMaxIndex = index), totalArea += area;
}));
let wordsSum = 0;
regions.forEach((region => {
const area = region.area, regionNum = Math.ceil(area / totalArea * words.length), regionWeight = area / areaMax;
region.words = [], region.regionNum = regionNum, region.regionWeight = regionWeight,
wordsSum += regionNum;
})), wordsSum < words.length && (regions[areaMaxIndex].wordsNum += words.length - wordsSum);
let currIndex = areaMaxIndex;
const regionNums = regions.map((region => region.regionNum));
words.forEach((word => {
let failCounter = 0, updateCounter = 0;
word.regionIndex = -1;
do {
if (regionNums[currIndex] > 0 && word.weight <= regions[currIndex].regionWeight) {
word.regionIndex = currIndex, regions[currIndex].words.push(word), regionNums[currIndex]--,
currIndex = (currIndex + 1) % regions.length;
break;
}
currIndex = (currIndex + 1) % regions.length, failCounter++, updateCounter++, updateCounter > regions.length + 1 && (regions.forEach((region => {
region.regionWeight += .15;
})), updateCounter = 0);
} while (-1 === word.regionIndex && failCounter < 3 * regions.length);
-1 === word.regionIndex && (word.regionIndex = areaMaxIndex, regions[areaMaxIndex].words.push(word),
regionNums[areaMaxIndex]--);
})), regions.forEach((region => {
region.words.sort(((a, b) => b.weight - a.weight));
}));
}
Object.defineProperty(exports, "__esModule", {
value: !0
}), exports.allocateWords = exports.scaleAndMiddleShape = exports.removeBorder = exports.segmentation = void 0,
exports.segmentation = segmentation, exports.removeBorder = removeBorder, exports.scaleAndMiddleShape = scaleAndMiddleShape,
exports.allocateWords = allocateWords;
//# sourceMappingURL=segmentation.js.map