import _ from 'lodash';
import Graphics from './graphics';
import { colors, EasingFunctions } from './variable';
import Text from './text';
import Tooltip from './tooltip';
import Utility from './utility';
// import Container from './_container';
/**
* Treemap Chart를 생성, 관리하는 클래스
* <p>ChartJS에서 지원하지 않는 타입의 차트라 직접 만들어줌
* @alias Treemap
*/
export default class Treemap {
/**
* @description Treemap차트에서 필요한 값들을 설정
* @param { object } option 차트 생성에 필요한 옵션
* @param { object } data 차트 생성에 필요한 데이터
*/
constructor(option, data) {
const self = this;
/**
* @description PIXI 오브젝트
* @type { PIXI }
*/
this.pixi = Utility.makePixi(option, true);
/**
* @description 값의 단위
* @type { string }
*/
this.unit = option.data.unit;
/**
* @description 최상단 DOM 사이즈
* @type { object }
*/
this.rootSize = option.rootSize;
/**
* @description 차트 데이터
* @type { object }
*/
this.data = data;
/**
* @description 현재 Treemap 깊이
* @type { number }
*/
this.depth = 1;
/**
* @description Treemap Animation 여부
* @type { boolean }
*/
this.animation = true;
/**
* @description Treemap 한단계 더 깊이 들어갈 때 펼쳐지는 효과 시간
* @type { number }
*/
this.expandDuration = 0.5;
/**
* @description 한단계 더 들어가서 새로운 Treemap 요소들이 펼쳐지는 시간
* @type { number }
*/
this.drawTreemapWithAnimationDuration = 0.5;
/**
* @description 새로운 요소들이 펼쳐지면서 각각이 온전한 상태로 변하는 시간
* @type { number }
*/
this.drawTreemapSeparatelyDuration = 0.3;
/**
* @description 새로운 요소들이 펼쳐지면서 각각이 온전한 상태로 변하는 길이
* @type { number }
*/
this.drawTreemapSeparatelyDistance = this.pixi.renderer.height / 60;
/**
* @description 현재 애니메이션 진행중 여부
* @type { boolean }
*/
this.animating = false;
/**
* @description 지워줘야 할 오브젝트 저장
* @type { array }
*/
this.deletableDepth = [];
/**
* @description 펼쳐지는 Treemap 요소 정보를 저장
* @type { array }
*/
this.expandInformation = [];
/**
* @description Treemap 차트에서 사용 될 Tooltip 오브젝트
* @type { Tooltip }
*/
this.tooltip = new Tooltip(this.pixi);
this.tooltip.setUnit(this.unit);
/**
* @description [Treemap-squared]{@link https://github.com/imranghory/treemap-squared}에서 사용된 컨테이너 개념
* @alias Container
* @memberof Treemap
*/
this.Container = class Container {
constructor(xoffset, yoffset, width, height) {
this.xoffset = xoffset;
this.yoffset = yoffset;
this.height = height;
this.width = width;
}
shortestEdge() {
return Math.min(this.height, this.width);
}
getCoordinates(row) {
const coordinates = [];
let subxoffset = this.xoffset;
let subyoffset = this.yoffset; // our offset within the container
const areawidth = self.sumArray(row) / this.height;
const areaheight = self.sumArray(row) / this.width;
let i;
if (this.width >= this.height) {
for (i = 0; i < row.length; i++) {
coordinates.push([subxoffset, subyoffset, subxoffset + areawidth, subyoffset + row[i] / areawidth]);
subyoffset += row[i] / areawidth;
}
} else {
for (i = 0; i < row.length; i++) {
coordinates.push([subxoffset, subyoffset, subxoffset + row[i] / areaheight, subyoffset + areaheight]);
subxoffset += row[i] / areaheight;
}
}
return coordinates;
}
cutArea(area) {
let newcontainer;
if (this.width >= this.height) {
const areawidth = area / this.height;
const newwidth = this.width - areawidth;
newcontainer = new Container(this.xoffset + areawidth, this.yoffset, newwidth, this.height);
} else {
const areaheight = area / this.width;
const newheight = this.height - areaheight;
newcontainer = new Container(this.xoffset, this.yoffset + areaheight, this.width, newheight);
}
return newcontainer;
}
};
const calculatedTreemap = this.calculateTreemap(this.data);
this.drawTreemap(calculatedTreemap, this.data);
}
/**
* @description Treemap의 데이터를 통해 그려야할 영역을 계산
* @param {object} datas Treemap의 데이터
* @memberof Treemap
* @instance
*/
calculateTreemap(datas) {
this.treemapData = [];
this.labels = [];
for (let i = 0; i < datas.length; i++) {
const eachData = datas[i];
const value = this.getSumOfChildrenData(eachData);
this.treemapData.push(value);
this.labels.push(eachData.label);
}
const size = this.rootSize;
const calculatedData = this.treemapMultidimensional(this.treemapData, size.width, size.height, 0, 0);
return calculatedData;
}
/**
* @description calculateTreemap 함수를 통해 계산된 데이터를 가지고 Treemap을 그려줌
* @param {object} calculatedData 계산된 Treemap 영역 데이터
* @param {object} originalDatas Treemap 오리지널 데이터
* @memberof Treemap
* @instance
*/
drawTreemap(calculatedData, originalDatas) {
let colorIndex = (this.depth - 1);
const objectsArray = [];
for (let i = 0; i < calculatedData.length; i++) {
const data = calculatedData[i];
const originalData = originalDatas[i];
const width = data[2] - data[0];
const height = data[3] - data[1];
const centerX = data[0] + width / 2;
const centerY = data[1] + height / 2;
const graphics = new Graphics(this.pixi);
const color = colors[colorIndex];
objectsArray.push(graphics);
graphics.setColor(color);
graphics.object.zOrder = -this.depth;
// graphics.setAlpha(0.7);
graphics.object.interactive = true;
if (_.isArray(originalData.data)) {
graphics.object.buttonMode = true;
}
graphics.drawRect(data[0], data[1], width, height);
const text = new Text(this.pixi, `${this.labels[i]}`);
text.object.zOrder = -this.depth;
// text.setColorBasedOnBackground(color);
// text.setColor([0, 0, 0]);
text.object.style.fill = 0x000000;
objectsArray.push(text);
const textScale = 0.5 * this.pixi.renderer.width * (1 / 339);
text.object.scale.set(textScale > 1 ? 1 : textScale);
if (text.object.width > width) {
text.object.text = '…';
}
text.setPosition(centerX, centerY);
colorIndex += 6;
if (colorIndex >= colors.length) {
colorIndex -= colors.length;
}
graphics.object.on('mouseover', () => {
this.tooltip.setData(originalData.label, this.getSumOfChildrenData(originalData));
this.tooltip.setPosition(centerX, centerY);
this.tooltip.show();
this.tooltipShowTime = new Date();
});
graphics.object.on('mouseout', () => {
if (new Date() - this.tooltipShowTime > 100) {
this.tooltip.hide();
}
});
// eslint-disable-next-line no-loop-func
graphics.object.on('pointerdown', (e) => {
const clickButton = e.data.originalEvent.button; // 0 = left, 1 = right
switch (clickButton) {
case 0:
if (graphics.object.buttonMode && this.animating === false) {
this.animating = true;
this.depth += 1;
this.tooltip.hide();
this.expand({
centerX,
centerY,
width,
height,
rootWidth: this.pixi.renderer.width,
rootHeight: this.pixi.renderer.height,
}, color);
const calculatedDataOfChild = this.calculateTreemap(originalData.data);
setTimeout(() => {
this.drawTreemapWithAnimation(calculatedDataOfChild, originalData.data);
}, this.expandDuration * 1000 - (this.drawTreemapWithAnimationDuration / calculatedDataOfChild.length) * 1000);
// this.drawTreemap(calculatedDataOfChild, originalData.data);
}
break;
case 2:
if (this.depth > 1) {
// this.deleteDepth();
// this.reduce();
}
break;
default:
break;
}
});
}
Utility.arrangePixiZOrder(this.pixi);
if (this.depth > 1) {
this.deletableDepth.push(objectsArray);
}
}
/**
* @description 애니메이션 효과와 함께 Treemap을 나타내기 위해 초기값을 설정하고 drawTreemapWithAnimationUpdate 함수를 실행시켜줌
* @param {object} calculatedData 계산된 Treemap 영역 데이터
* @param {object} originalDatas Treemap 오리지널 데이터
* @memberof Treemap
* @instance
*/
drawTreemapWithAnimation(calculatedData, originalDatas) {
if (this.drawTreemapWithAnimationUpdateInterval === undefined) {
this.drawTreemapWithAnimationIndexArray = [];
this.drawTreemapWithAnimationCheckPoint = 1 / calculatedData.length;
const objectsArray = [];
const colorIndex = this.depth - 1;
this.drawTreemapWithAnimationStartTime = new Date();
this.drawTreemapWithAnimationUpdateInterval = setInterval(() => { this.drawTreemapWithAnimationUpdate(calculatedData, originalDatas, colorIndex, objectsArray); }, 16);
}
}
/**
* @description 애니메이션 효과와 함께 Treemap을 나타내줌
* @param {object} calculatedData 계산된 Treemap 영역 데이터
* @param {object} originalDatas Treemap 오리지널 데이터
* @param {number} colorIndex 색상 인덱스
* @param {object} objectsArray 오브젝트를 담을 배열
* @memberof Treemap
* @instance
*/
drawTreemapWithAnimationUpdate(calculatedData, originalDatas, colorIndex, objectsArray) {
const elapsedTime = new Date() - this.drawTreemapWithAnimationStartTime;
const progress = elapsedTime / (this.drawTreemapWithAnimationDuration * 1000);
const checkPointLength = 1 / calculatedData.length;
if (progress >= this.drawTreemapWithAnimationCheckPoint) {
const index = Math.round(this.drawTreemapWithAnimationCheckPoint / checkPointLength) - 1; // 순서대로 인덱스
// 랜덤 인덱스
// let index;
// do {
// index = Math.floor(Math.random() * calculatedData.length);
// } while (this.drawTreemapWithAnimationIndexArray.includes(index));
// this.drawTreemapWithAnimationIndexArray.push(index);
const graphics = new Graphics(this.pixi);
graphics.object.zOrder = -this.depth;
graphics.object.interactive = true;
if (_.isArray(originalDatas[index].data)) {
graphics.object.buttonMode = true;
}
objectsArray.push(graphics);
const width = calculatedData[index][2] - calculatedData[index][0];
const height = calculatedData[index][3] - calculatedData[index][1];
const centerX = calculatedData[index][0] + width / 2;
const centerY = calculatedData[index][1] + height / 2;
const text = new Text(this.pixi, `${originalDatas[index].label}`);
objectsArray.push(text);
text.object.zOrder = -this.depth;
const textScale = 0.5 * this.pixi.renderer.width * (1 / 339);
text.object.scale.set(textScale > 1 ? 1 : textScale);
if (text.object.width > calculatedData) {
text.object.text = '…';
}
text.setPosition(centerX, centerY);
text.object.setAlpha = 0;
colorIndex = 6 * index + colorIndex;
if (colorIndex >= colors.length) {
colorIndex -= colors.length * Math.floor(colorIndex / colors.length);
}
const color = colors[colorIndex];
graphics.object.on('mouseover', () => {
this.tooltip.setData(originalDatas[index].label, this.getSumOfChildrenData(originalDatas[index]));
this.tooltip.setPosition(centerX, centerY);
this.tooltip.show();
this.tooltipShowTime = new Date();
});
graphics.object.on('mouseout', () => {
if (new Date() - this.tooltipShowTime > 100) {
this.tooltip.hide();
}
});
graphics.object.on('pointerdown', (e) => {
const clickButton = e.data.originalEvent.button; // 0 = left, 1 = right
switch (clickButton) {
case 0:
if (graphics.object.buttonMode && this.animating === false) {
this.animating = true;
this.depth += 1;
this.tooltip.hide();
this.expand({
centerX,
centerY,
width,
height,
rootWidth: this.pixi.renderer.width,
rootHeight: this.pixi.renderer.height,
}, color);
const calculatedDataOfChild = this.calculateTreemap(originalDatas[index].data);
setTimeout(() => {
this.drawTreemapWithAnimation(calculatedDataOfChild, originalDatas[index].data);
}, this.expandDuration * 1000);
// this.drawTreemap(calculatedDataOfChild, originalData.data);
}
break;
case 2:
if (this.depth > 1) {
if (this.animating === false) {
this.tooltip.hide();
this.animating = true;
const tempArray = [];
for (let j = 1; j < this.deletableDepth[this.depth - 2].length; j += 2) {
const eachGraphics = this.deletableDepth[this.depth - 2][j - 1];
const eachText = this.deletableDepth[this.depth - 2][j];
tempArray.push({ graphics: eachGraphics, text: eachText });
this.hideTreemapWithAnimation(calculatedData, originalDatas, tempArray);
}
setTimeout(() => {
this.reduce();
}, this.drawTreemapWithAnimationDuration * 1000 + this.drawTreemapSeparatelyDuration * 1000);
}
}
break;
default:
break;
}
});
this.drawTreemapSeparately({ graphics, text }, {
calculatedData: calculatedData[index],
originalDatas: originalDatas[index],
color,
}, 1);
this.drawTreemapWithAnimationCheckPoint += checkPointLength;
}
if (progress >= 1) {
clearInterval(this.drawTreemapWithAnimationUpdateInterval);
this.deletableDepth.push(objectsArray);
setTimeout(() => { this.animating = false; }, this.drawTreemapSeparatelyDuration * 1000);
delete this.drawTreemapWithAnimationUpdateInterval;
}
}
/**
* @description 애니메이션 효과와 함께 Treemap을 숨기기 위해 초기값을 설정하고 hideTreemapWithAnimationUpdate 함수를 실행시켜줌
* @param {object} calculatedData 계산된 Treemap 영역 데이터
* @param {object} originalDatas Treemap 오리지널 데이터
* @param {objects} objects drawTreemapWithAnimationUpdate에서 담아두었던 오브젝트들
* @memberof Treemap
* @instance
*/
hideTreemapWithAnimation(calculatedData, originalDatas, objects) {
this.hideTreemapWithAnimationCheckPoint = 1 / calculatedData.length;
if (this.hideTreemapWithAnimationUpdateInterval === undefined) {
this.hideTreemapWithAnimationStartTime = new Date();
this.hideTreemapWithAnimationUpdateInterval = setInterval(() => { this.hideTreemapWithAnimationUpdate(calculatedData, originalDatas, objects); }, 16);
}
this.tempvalue = 0;
}
/**
* @description 애니메이션 효과와 함께 Treemap을 숨김
* @param {object} calculatedData 계산된 Treemap 영역 데이터
* @param {object} originalDatas Treemap 오리지널 데이터
* @param {objects} objects drawTreemapWithAnimationUpdate에서 담아두었던 오브젝트들
* @memberof Treemap
* @instance
*/
hideTreemapWithAnimationUpdate(calculatedData, originalDatas, objects) {
const elapsedTime = new Date() - this.hideTreemapWithAnimationStartTime;
const progress = elapsedTime / (this.drawTreemapWithAnimationDuration * 1000);
const checkPointLength = 1 / calculatedData.length;
if (progress >= this.hideTreemapWithAnimationCheckPoint) {
const index = calculatedData.length - Math.round(this.hideTreemapWithAnimationCheckPoint / checkPointLength); // 순서대로 인덱스
this.drawTreemapSeparately(objects[index], {
calculatedData: calculatedData[index],
originalDatas: originalDatas[index],
}, -1);
this.hideTreemapWithAnimationCheckPoint += checkPointLength;
}
if (progress >= 1) {
clearInterval(this.hideTreemapWithAnimationUpdateInterval);
delete this.hideTreemapWithAnimationUpdateInterval;
}
}
/**
* @description 애니메이션 효과와 함께 Treemap을 나타낼 때 개별적으로 Treemap 요소를 그려주기 위해 셋팅
* @param {object} object Treemap 개별 요소의 오브젝트
* @param {object} datas Treemap 요소 데이터
* @param {objects} visible 1 = show , -1 = hide
* @memberof Treemap
* @instance
*/
drawTreemapSeparately(object, datas, visible) {
const startTime = new Date();
visible = visible || 1;
if (!_.isUndefined(object)) {
const interval = setInterval(() => {
this.drawTreemapSeparatelyUpdate(object, datas, startTime, interval, visible);
}, 16);
}
}
/**
* @description 애니메이션 효과와 함께 Treemap을 나타낼 때 개별적으로 Treemap 요소를 그려줌
* @param {object} object Treemap 개별 요소의 오브젝트
* @param {object} datas Treemap 요소 데이터
* @param {date} startTime 시작 시간
* @param {number} interval setInterval 인덱스
* @param {objects} visible 1 = show , -1 = hide
* @memberof Treemap
* @instance
*/
drawTreemapSeparatelyUpdate(object, datas, startTime, interval, visible) {
const elapsedTime = new Date() - startTime;
const progress = elapsedTime / (this.drawTreemapSeparatelyDuration * 1000) > 1 ? 1 : elapsedTime / (this.drawTreemapSeparatelyDuration * 1000);
const progressOnVisible = visible > 0 ? progress : 1 - progress;
const drawX = datas.calculatedData[0] + (this.drawTreemapSeparatelyDistance - this.drawTreemapSeparatelyDistance * progressOnVisible);
const drawWidth = datas.calculatedData[2] - drawX;
const drawY = datas.calculatedData[1] - (this.drawTreemapSeparatelyDistance - this.drawTreemapSeparatelyDistance * progressOnVisible);
const drawHeight = datas.calculatedData[3] - datas.calculatedData[1];
const color = visible > 0 ? datas.color : object.graphics.colorRGB;
object.graphics.object.clear();
object.graphics.setAlpha(progressOnVisible);
object.graphics.setColor(color);
object.graphics.drawRect(drawX, drawY, drawWidth, drawHeight);
object.text.setAlpha(progressOnVisible);
// object.text.setColorBasedOnBackground(color);
object.text.setPosition(drawWidth / 2 + drawX, drawHeight / 2 + drawY);
if (progress >= 1 || _.isUndefined(object)) {
clearInterval(interval);
// if (visible === -1) {
// object.graphics.destroy();
// object.text.destroy();
// delete object.graphics;
// delete object.text;
// }
Utility.arrangePixiZOrder(this.pixi);
}
}
/**
* @description Treemap의 깊이 레벨이 낮아질 때 확대되었던 요소를 축소시켜줌
* @memberof Treemap
* @instance
*/
reduce() {
const expandInformation = this.expandInformation.pop();
const { graphics, calculatedSquare, color } = expandInformation;
if (this.expandUpdateInterval === undefined) {
this.expandStartTime = new Date();
}
if (this.expandUpdateInterval === undefined) {
this.expandUpdateInterval = setInterval(() => { this.expandUpdate(calculatedSquare, color, graphics, -1); }, 16);
}
}
/**
* @description Treemap의 깊이 레벨이 높아질 때 요소를 확대시켜주기 위해 데이터 셋팅
* @memberof Treemap
* @param {object} square 요소 사각형 정보
* @param {number} color 색상 값
* @instance
*/
expand(square, color) {
if (this.expandUpdateInterval === undefined) {
const background = new Graphics(this.pixi);
background.object.zOrder = -this.depth;
Utility.arrangePixiZOrder(this.pixi);
this.expandStartTime = new Date();
const squareLeft = square.centerX - square.width / 2;
const squareRight = square.centerX + square.width / 2;
const squareTop = square.centerY - square.height / 2;
const squareBottom = square.centerY + square.height / 2;
const leftDistance = squareLeft;
const rightDistance = square.rootWidth - squareRight;
const topDistance = squareTop;
const bottomDistance = square.rootHeight - squareBottom;
const calculatedSquare = {
left: squareLeft,
right: squareRight,
top: squareTop,
bottom: squareBottom,
distance: {
left: leftDistance,
right: rightDistance,
top: topDistance,
bottom: bottomDistance,
},
width: square.width,
height: square.height,
};
this.expandInformation.push({ graphics: background, calculatedSquare, color });
this.expandUpdateInterval = setInterval(() => { this.expandUpdate(calculatedSquare, color, background, 1); }, 16);
}
}
/**
* @description Treemap의 깊이 레벨이 높아질 때 요소를 확대/축소
* @memberof Treemap
* @param {object} square 요소 사각형 정보
* @param {number} color 색상 값
* @param {Graphics} background 요소를 그려주는 Graphics 오브젝트
* @param {number} mode 확대 or 축소
* @instance
*/
expandUpdate(square, color, background, mode) {
const elapsedTime = new Date() - this.expandStartTime;
const easingTime = elapsedTime / (this.expandDuration * 1000);
const easing = EasingFunctions.easeOutQuart;
const progress = easingTime < 1 ? easing(easingTime) : 1;
const applyProgress = mode > 0 ? progress : 1 - progress;
const drawX = square.left - square.distance.left * applyProgress;
const drawY = square.top - square.distance.top * applyProgress;
const drawWidth = square.width + square.distance.right * applyProgress + square.distance.left * applyProgress;
const drawHeight = square.height + square.distance.bottom * applyProgress + square.distance.top * applyProgress;
const rDistance = 255 - color[0];
const gDistance = 255 - color[1];
const bDistance = 255 - color[2];
const drawR = color[0] + Math.floor(rDistance * applyProgress);
const drawG = color[1] + Math.floor(gDistance * applyProgress);
const drawB = color[2] + Math.floor(bDistance * applyProgress);
// eslint-disable-next-line no-unused-vars
const drawColor = [drawR, drawG, drawB];
const drawColorTemp = [color[0], color[1], color[2]];
background.object.clear();
background.setColor(drawColorTemp);
background.drawRect(drawX, drawY, drawWidth, drawHeight);
if (progress >= 1) {
clearInterval(this.expandUpdateInterval);
delete this.expandUpdateInterval;
if (mode === -1) {
this.animating = false;
this.deleteDepth();
background.destroy();
}
}
}
/**
* @description Treemap의 깊이 레벨이 한단계 낮춰줌
* @memberof Treemap
* @instance
*/
deleteDepth() {
const objectsArray = this.deletableDepth.pop();
for (let i = 0; i < objectsArray.length; i++) {
const object = objectsArray[i];
object.destroy();
delete objectsArray[i];
}
this.depth -= 1;
}
/**
* @description [Treemap-squared]{@link https://github.com/imranghory/treemap-squared}에서 가져옴
* <p> Gamify에 맞게 변형
* @memberof Treemap
* @instance
*/
treemapSingledimensional(data, width, height, xoffset, yoffset) {
xoffset = (typeof xoffset === 'undefined') ? 0 : xoffset;
yoffset = (typeof yoffset === 'undefined') ? 0 : yoffset;
const rawtreemap = this.squarify(this.normalize(data, width * height), [], new this.Container(xoffset, yoffset, width, height), []);
return this.flattenTreemap(rawtreemap);
}
/**
* @description [Treemap-squared]{@link https://github.com/imranghory/treemap-squared}에서 가져옴
* <p> Gamify에 맞게 변형
* @memberof Treemap
* @instance
*/
treemapMultidimensional(data, width, height, xoffset, yoffset) {
xoffset = (typeof xoffset === 'undefined') ? 0 : xoffset;
yoffset = (typeof yoffset === 'undefined') ? 0 : yoffset;
const mergeddata = [];
let mergedtreemap;
let results = [];
let i;
if (_.isArray(data[0])) { // if we've got more dimensions of depth
for (i = 0; i < data.length; i++) {
mergeddata[i] = this.sumMultidimensionalArray(data[i]);
}
mergedtreemap = this.treemapSingledimensional(mergeddata, width, height, xoffset, yoffset);
for (i = 0; i < data.length; i++) {
results.push(this.treemapMultidimensional(data[i], mergedtreemap[i][2] - mergedtreemap[i][0], mergedtreemap[i][3] - mergedtreemap[i][1], mergedtreemap[i][0], mergedtreemap[i][1]));
}
} else {
results = this.treemapSingledimensional(data, width, height, xoffset, yoffset);
}
return results;
}
/**
* @description 자신과 자식들의 데이터 총합을 구해줌
* @param {object} object 데이터 오브젝트
* @memberof Treemap
* @instance
*/
getSumOfChildrenData(object) {
let sum = 0;
if (_.isArray(object.data)) {
for (let i = 0; i < object.data.length; i++) {
sum += this.getSumOfChildrenData(object.data[i]);
}
} else {
sum = parseInt(object.data, 10);
}
return sum;
}
/**
* @description [Treemap-squared]{@link https://github.com/imranghory/treemap-squared}에서 가져옴
* <p> Gamify에 맞게 변형
* @memberof Treemap
* @instance
*/
squarify(data, currentrow, container, stack) {
let newcontainer;
if (data.length === 0) {
stack.push(container.getCoordinates(currentrow));
return;
}
const length = container.shortestEdge();
const nextdatapoint = data[0];
if (this.improvesRatio(currentrow, nextdatapoint, length)) {
currentrow.push(nextdatapoint);
this.squarify(data.slice(1), currentrow, container, stack);
} else {
newcontainer = container.cutArea(this.sumArray(currentrow), stack);
stack.push(container.getCoordinates(currentrow));
this.squarify(data, [], newcontainer, stack);
}
// eslint-disable-next-line consistent-return
return stack;
}
/**
* @description [Treemap-squared]{@link https://github.com/imranghory/treemap-squared}에서 가져옴
* <p> Gamify에 맞게 변형
* @memberof Treemap
* @instance
*/
normalize(data, area) {
const normalizeddata = [];
const sum = this.sumArray(data);
const multiplier = area / sum;
let i;
for (i = 0; i < data.length; i++) {
normalizeddata[i] = data[i] * multiplier;
}
return normalizeddata;
}
/**
* @description [Treemap-squared]{@link https://github.com/imranghory/treemap-squared}에서 가져옴
* <p> Gamify에 맞게 변형
* @memberof Treemap
* @instance
*/
flattenTreemap(rawtreemap) {
const flattreemap = [];
let i; let
j;
for (i = 0; i < rawtreemap.length; i++) {
for (j = 0; j < rawtreemap[i].length; j++) {
flattreemap.push(rawtreemap[i][j]);
}
}
return flattreemap;
}
/**
* @description [Treemap-squared]{@link https://github.com/imranghory/treemap-squared}에서 가져옴
* <p> Gamify에 맞게 변형
* @memberof Treemap
* @instance
*/
improvesRatio(currentrow, nextnode, length) {
if (currentrow.length === 0) {
return true;
}
const newrow = currentrow.slice();
newrow.push(nextnode);
const currentratio = this.calculateRatio(currentrow, length);
const newratio = this.calculateRatio(newrow, length);
// the pseudocode in the Bruls paper has the direction of the comparison
// wrong, this is the correct one.
return currentratio >= newratio;
}
/**
* @description [Treemap-squared]{@link https://github.com/imranghory/treemap-squared}에서 가져옴
* <p> Gamify에 맞게 변형
* @memberof Treemap
* @instance
*/
calculateRatio(row, length) {
const min = Math.min(...row);
const max = Math.max(...row);
const sum = this.sumArray(row);
return Math.max((length ** 2) * max / (sum ** 2), (sum ** 2) / ((length ** 2) * min));
}
/**
* @description [Treemap-squared]{@link https://github.com/imranghory/treemap-squared}에서 가져옴
* <p> Gamify에 맞게 변형
* @memberof Treemap
* @instance
*/
sumArray(arr) {
let sum = 0;
let i;
for (i = 0; i < arr.length; i++) {
sum += arr[i];
}
return sum;
}
/**
* @description [Treemap-squared]{@link https://github.com/imranghory/treemap-squared}에서 가져옴
* <p> Gamify에 맞게 변형
* @memberof Treemap
* @instance
*/
sumMultidimensionalArray(arr) {
let i; let
total = 0;
if (_.isArray(arr[0])) {
for (i = 0; i < arr.length; i++) {
total += this.sumMultidimensionalArray(arr[i]);
}
} else {
total = this.sumArray(arr);
}
return total;
}
}