angular-sunburst-radar-chart
Version:
A Sunburst Radar chart with SVG,No Dependencies
928 lines (912 loc) • 44.6 kB
JavaScript
import { __decorate } from 'tslib';
import { Input, Component, NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
function hashCode(obj) {
let h = 0;
obj = getOptionsOrEmpty(obj);
let s = JSON.stringify(obj);
for (let i = 0; i < s.length; i++) {
h = Math.imul(31, h) + s.charCodeAt(i) | 0;
}
return h;
}
function getOptionsOrEmpty(options) {
return options || {};
}
function getItemTitle(item) {
item = item || { name: '', value: '' };
const dash = item.name.length > 0 ? '-' : '';
return item.name + dash + item.value;
}
function clone(obj) {
return JSON.parse(JSON.stringify(obj));
}
function generateRandomColor() {
const letters = '0123456789ABCDEF';
let color = '#';
for (let i = 0; i < 6; i++) {
color += letters[(Math.floor(Math.random() * 16))];
}
return color;
}
function formatItems(items) {
return items.map(item => {
if (!!item.children && item.children.length > 0) {
}
else {
item['children'] = [];
item['children'].push({ name: '', value: 0 });
}
return item;
});
}
function getFormattedAngle(angle, center) {
const [centerX, centerY] = [center.x, center.y];
return 'rotate(' + angle + ' ' + centerX + ' ' + centerY + ')';
}
function getCurrentPointFromEvent(evt) {
let [x, y] = [evt.clientX, evt.clientY];
if (evt.targetTouches && evt.targetTouches[0]) {
[x, y] = [evt.targetTouches[0].pageX, evt.targetTouches[0].pageY];
}
return { x, y };
}
function createCircle(options) {
const defaults = {
x: 0,
y: 0,
radius: 0,
fillColor: 'none',
'stroke-width': '1',
'stroke': '#000000',
'stroke-dasharray': 'none',
'stroke-opacity': '1',
'title': ''
};
options = Object.assign(Object.assign({}, defaults), (getOptionsOrEmpty(options)));
const circle = { name: 'circle', options, children: [] };
return circle;
}
function createLine(options) {
const defaults = { x1: 0, y1: 0, x2: 0, y2: 0, color: '#000000', width: '2', title: '' };
options = Object.assign(Object.assign({}, defaults), (getOptionsOrEmpty(options)));
const line = { name: 'line', options, children: [] };
return line;
}
function createPath(options) {
const defaults = { d: '', fill: 'none', stroke: 'none', 'stroke-width': '0', title: '', id: null };
options = Object.assign(Object.assign({}, defaults), (getOptionsOrEmpty(options)));
const { d, color, borderColor } = options;
const path = { name: 'path', options, children: [] };
return path;
}
function createPathForBar(options) {
const defaults = {
d: '',
fill: 'none',
stroke: 'none',
'stroke-width': '0',
'stroke-opacity': '1.0',
'fill-opacity': '1.0',
title: '',
id: null
};
options = Object.assign(Object.assign({}, defaults), (getOptionsOrEmpty(options)));
const { d, color, borderColor } = options;
let gradName = options['fill'];
gradName = gradName.replace('#', '');
options['gradientId'] = gradName;
options['fillUrl'] = 'url(#' + gradName + ')';
const path = { name: 'path-bar', options, children: [] };
return path;
}
function polarToCartesian(centerX, centerY, radius, angleInDegrees) {
const adjustedViewPortAngle = (angleInDegrees - 90);
const angleInRadians = adjustedViewPortAngle * Math.PI / 180.0;
return {
x: centerX + (radius * Math.cos(angleInRadians)),
y: centerY + (radius * Math.sin(angleInRadians))
};
}
function getLargeArcFlag(startAngle, endAngle) {
return (endAngle - startAngle) <= 180 ? '0' : '1';
}
function distanceBetweenTwoPoints(centerX, centerY, radius, startAngle, endAngle) {
const startPoint = polarToCartesian(centerX, centerY, radius, startAngle);
const endPoint = polarToCartesian(centerX, centerY, radius, endAngle);
const distFromStartToEnd = Math.sqrt(Math.pow((startPoint.x - endPoint.x), 2) + Math.pow((startPoint.y - endPoint.y), 2));
return Math.abs(distFromStartToEnd);
}
function calculateAngleRadian({ x, y, centerX, centerY, maxRad }) {
let angleInRadian = Math.atan2(x - centerY, y - centerX);
angleInRadian = adjustAngleRadianDifference(angleInRadian, maxRad);
return angleInRadian;
}
function adjustAngleRadianDifference(input, maxRad) {
let angleInRadian = input;
angleInRadian += maxRad / 4;
if (angleInRadian < 0) {
angleInRadian += maxRad;
}
return angleInRadian;
}
function createArcToWriteText({ startPoint, radius, id, startAngle, endAngle }) {
const [centerX, centerY] = [startPoint.x, startPoint.y];
const start = polarToCartesian(centerX, centerY, radius, endAngle);
const end = polarToCartesian(centerX, centerY, radius, startAngle);
const largeArcFlag = getLargeArcFlag(startAngle, endAngle);
const d = [
'M', start.x, start.y,
'A', radius, radius, 0, largeArcFlag, 0, end.x, end.y
].join(' ');
return createPath({ d, borderColor: '', id });
}
function getTextForAngle(text, distance, fontSize) {
let result = text;
let perCharacter = 10.07;
if (fontSize < 18) {
perCharacter = 8.7;
}
const totalTextLength = Math.round(distance / perCharacter);
if (text.length > 0 && text.length > totalTextLength) {
result = text.substring(0, totalTextLength - 1) + '..';
}
return result;
}
function writeTextOnArc(options) {
const defaults = { text: '', label: '', pathId: '', 'font-size': '14px', };
options = Object.assign(Object.assign({}, defaults), (getOptionsOrEmpty(options)));
const { text, pathId, label } = options;
if (pathId !== '') {
options['href'] = '#' + pathId;
options['startOffset'] = '50%';
options['text-anchor'] = 'middle';
options['title'] = text;
}
const textOnArc = { name: 'text-on-arc', options, children: [] };
return textOnArc;
}
function createText(options) {
const defaults = {
content: '', x: 0, y: 0,
stroke: 'white',
'stroke-width': '1px',
'font-size': '6px',
'text-anchor': 'middle'
};
options = Object.assign(Object.assign({}, defaults), (getOptionsOrEmpty(options)));
const textElement = { name: 'text', options, children: [] };
return textElement;
}
function convertToPercentage(input) {
let { plotMax, actualScore, maxScore } = input;
if (actualScore > maxScore) {
actualScore = maxScore;
}
const perValue = plotMax / maxScore;
return actualScore * perValue;
}
function createOuterChartBarWithInArc({ item, startAngle, endAngle, middleAngle, color, middleRadius, maxScore, innerRadiusBorder, center }) {
const [centerX, centerY] = [center.x, center.y];
const currentVal = item.value;
const totalRadiusInside = middleRadius - innerRadiusBorder;
const innerRadius = convertToPercentage({ plotMax: totalRadiusInside, actualScore: currentVal, maxScore });
const radiusFromCenter = innerRadius + innerRadiusBorder;
const firstPoint = polarToCartesian(centerX, centerY, radiusFromCenter, startAngle);
const secondPoint = polarToCartesian(centerX, centerY, radiusFromCenter, endAngle);
const startPoint = polarToCartesian(centerX, centerY, innerRadiusBorder, startAngle);
const endPoint = polarToCartesian(centerX, centerY, innerRadiusBorder, endAngle);
const startMiddlePoint = polarToCartesian(centerX, centerY, innerRadiusBorder, middleAngle);
const distFromStartToFirst = Math.sqrt(Math.pow((startPoint.x - firstPoint.x), 2) + Math.pow((startPoint.y - firstPoint.y), 2));
const distFromStartToSecond = Math.sqrt(Math.pow((startPoint.x - secondPoint.x), 2) + Math.pow((startPoint.y - secondPoint.y), 2));
const { updatedFirstPoint, updatedSecondPoint } = getUpdatedPoints(firstPoint, secondPoint, distFromStartToFirst, distFromStartToSecond);
const d = getDrawPositions(updatedFirstPoint, middleRadius, updatedSecondPoint, endPoint, startMiddlePoint, startPoint);
const title = getItemTitle(item);
return createPathForBar({ d: d.join(' '), stroke: 'none', fill: color, "fill-opacity": '0.5', title });
}
function getDrawPositions(firstPoint, middleRadius, secondPoint, endPoint, startMiddlePoint, startPoint) {
const d = [
'M', firstPoint.x, firstPoint.y,
'A', middleRadius, middleRadius, 0, 0, 1, secondPoint.x, secondPoint.y,
'L', endPoint.x, endPoint.y,
'C', endPoint.x, endPoint.y, startMiddlePoint.x, startMiddlePoint.y, startPoint.x, startPoint.y,
'L', firstPoint.x, firstPoint.y,
'Z'
];
return d;
}
function getUpdatedPoints(firstPoint, secondPoint, distFromStartToFirst, distFromStartToSecond) {
let [updatedFirstPoint, updatedSecondPoint] = [firstPoint, secondPoint];
if (distFromStartToSecond < distFromStartToFirst) {
updatedSecondPoint = firstPoint;
updatedFirstPoint = secondPoint;
}
return { updatedSecondPoint, updatedFirstPoint };
}
function createInnerChartBarWithInArc({ startPoint, item, radius, startAngle, endAngle, maxScore }) {
const [centerX, centerY] = [startPoint.x, startPoint.y];
const currentVal = item.value;
const color = item.color;
const arcRadius = convertToPercentage({ plotMax: radius, actualScore: currentVal, maxScore });
const start = polarToCartesian(centerX, centerY, arcRadius, endAngle);
const end = polarToCartesian(centerX, centerY, arcRadius, startAngle);
const largeArcFlag = getLargeArcFlag(startAngle, endAngle);
//const largeArcFlag = endAngle - startAngle <= 180 ? '0' : '1';
const d = [
'M', centerX, centerY,
'L', start.x, start.y,
'A', arcRadius, arcRadius, 0, largeArcFlag, 0, end.x, end.y
].join(' ');
const title = getItemTitle(item);
const arcForInnerChart = createPathForBar({ d, fill: color, title });
return arcForInnerChart;
}
function getRadiusForBorderAndText(radius, borderHeight) {
const borderRadius = radius + borderHeight;
const textRadius = radius + ((borderRadius - radius) / 2);
return [borderRadius, textRadius];
}
function getMaxDepth(items, currentLevel = 0) {
let depths = [];
if (items && items.length > 0) {
const nextLevel = currentLevel + 1;
depths = items
.filter(item => !!item.children)
.filter(item => item.children.length > 0)
.map(item => getMaxDepth(item.children, nextLevel));
}
return depths.length !== 0 ? Math.max(...depths) : currentLevel;
}
function getGlobalPositions({ size, maxScore, items }) {
const center = { x: size / 2, y: size / 2 };
const maxDepth = getMaxDepth(items);
const totalLevels = maxDepth + 1;
const innerCircleRadius = Math.abs(size / 5.33);
const totalRadiusToBeDrawn = 2 * Math.abs(size / 5.33);
const borderHeight = 0.0375 * size;
const everyLevelRadius = totalRadiusToBeDrawn / totalLevels;
const levels = [];
for (let i = 1; i <= totalLevels; i++) {
const startRadius = i * everyLevelRadius;
const [endRadius, textRadius] = getRadiusForBorderAndText(startRadius, borderHeight);
levels.push({ startRadius, endRadius, textRadius });
}
const innerRadius = innerCircleRadius;
const [innerRadiusBorder, innerTextRadius] = getRadiusForBorderAndText(innerRadius, borderHeight);
const middleRadius = innerCircleRadius * 2;
const [middleRadiusBorder, middleTextRadius] = getRadiusForBorderAndText(middleRadius, borderHeight);
const outerRadius = middleRadius + borderHeight;
const [outerRadiusBorder, outerTextRadius] = getRadiusForBorderAndText(outerRadius, borderHeight * 2);
const textSize = 0.0175 * size;
const outerTextSize = 0.0225 * size;
const result = {
textSize,
outerTextSize,
levels,
innerRadius,
innerRadiusBorder,
innerTextRadius,
middleRadius,
middleTextRadius,
middleRadiusBorder,
outerRadius,
outerRadiusBorder,
outerTextRadius,
center
};
return result;
}
function calculatePointBetween({ centerX, centerY, startAngle, middleAngle, endAngle, radius }) {
const start = polarToCartesian(centerX, centerY, radius, startAngle);
const middle = polarToCartesian(centerX, centerY, radius, middleAngle);
const end = polarToCartesian(centerX, centerY, radius, endAngle);
return { start, middle, end };
}
function createLegendWithOptions({ startPoint, center, startRadius, endRadius, degreeToBeDrawn, maxScore }) {
const [centerX, centerY] = [center.x, center.y];
const actRadius = endRadius - startRadius;
const axisIncrement = maxScore / 4;
const legendRadius = convertToPercentage({ plotMax: actRadius, actualScore: axisIncrement, maxScore });
const smallCircleRadius = Math.round(0.083 * actRadius);
const smallCircleFontSize = Math.round(0.067 * actRadius);
const groups = [1, 2, 3]
.map(val => {
return { radius: val * legendRadius, content: Math.round(val * axisIncrement) };
})
.map(res => {
const { radius, content } = res;
const { x, y } = polarToCartesian(startPoint.x, startPoint.y, radius, degreeToBeDrawn);
const circle = createCircle({
x,
y,
radius: smallCircleRadius,
'fillColor': '#000000'
});
const legendCircle = createCircle({
x: centerX,
y: centerY,
radius: startRadius + radius,
'stroke-dasharray': 4,
'stroke-opacity': 0.3
});
const fontSize = smallCircleFontSize + "px";
//console.log("font-size",fontSize)
const text = createText({ content, x, y, 'stroke': 'white', 'font-size': fontSize });
return [circle, legendCircle, text];
});
return [].concat.apply([], groups);
}
function createLegends({ startPoint, radius, degreeToBeDrawn, maxScore }) {
return createLegendWithOptions({ startPoint, center: startPoint, startRadius: 0, endRadius: radius, degreeToBeDrawn, maxScore });
}
//Not Used now ,Might Use later
// function anglesBasedOnPercentage(items) {
//
// const total = items.reduce((prevValue, curValue) => prevValue +
//curValue, 0);
// if (Math.round(total) != 100) {
// console.error('Cannnot draw as the sum of values should be 100');
// throw new Error('Cannnot draw as the sum of values should be 100');
// }
// let prevDegree = 0;
// const results = [];
// for (let i = 0; i < items.length; i++) {
//
// const item = items[i];
// const currentDegree = item * 3.6;
//
// const degreeToBeDrawn = currentDegree + prevDegree;
// results.push(degreeToBeDrawn);
// prevDegree = prevDegree + currentDegree;
// }
//
//
// return results;
// }
//
function getInitialPosition(startLocation, totalDegrees, num) {
const perLocation = totalDegrees / num;
const middleLocation = perLocation / 2;
const startMiddle = startLocation + middleLocation;
return { startLocation, perLocation, middleLocation, startMiddle };
}
function getDegreeForIndexBasedOnSplitAngle(index, data) {
const { perLocation, middleLocation, startMiddle, startLocation } = data;
let position = index; // + 1;
const startDegree = startLocation + (position * perLocation);
const middleDegree = startMiddle + (position * perLocation);
const endDegree = startLocation + (position * perLocation) + perLocation;
return { startDegree, middleDegree, endDegree };
}
function getAllAnglesBasedOnChild(items) {
const num = items.length;
const results = [];
const totalChildren = items.filter(item => item.children).map(item => item.children.length).reduce(((a, b) => a + b), 0);
const childAngleDifference = 360 / totalChildren;
let currentStartLocation = 0;
for (let i = 0; i < items.length; i++) {
const item = items[i];
const childCount = item.children ? item.children.length : 0;
const totalDegrees = childCount * childAngleDifference;
const endDegree = currentStartLocation + (childCount * childAngleDifference);
const middleDegree = (endDegree - currentStartLocation) / 2;
const currentItem = {};
currentItem['item'] = item;
currentItem['startDegree'] = currentStartLocation;
currentItem['middleDegree'] = middleDegree;
currentItem['endDegree'] = endDegree;
if (childCount > 0) {
currentItem['children'] = iterateChildrenAndSetAngles({ children: item.children, currentItem, angleDifference: totalDegrees });
}
currentStartLocation = currentStartLocation + (childCount * childAngleDifference);
results.push(currentItem);
}
return results;
}
function getAllAnglesBasedOnParent(items) {
const num = items.length;
let results = [];
let angleDifference = 360 / num;
const data = getInitialPosition(0, 360, num);
for (let i = 0; i < items.length; i++) {
const { startDegree, middleDegree, endDegree } = getDegreeForIndexBasedOnSplitAngle(i, data);
const item = items[i];
const currentItem = {};
currentItem['item'] = item;
currentItem['startDegree'] = startDegree;
currentItem['middleDegree'] = middleDegree;
currentItem['endDegree'] = endDegree;
const hasChildren = items.filter(item => !!item.children).length > 0;
if (hasChildren) {
currentItem['children'] = iterateChildrenAndSetAngles({ children: item.children, currentItem, angleDifference });
}
results.push(currentItem);
}
return results;
}
function iterateChildrenAndSetAngles({ children, currentItem, angleDifference }) {
const count = children.length;
const childItems = [];
const data = getInitialPosition(currentItem['startDegree'], angleDifference, count);
for (let j = 0; j < count; j++) {
const childItem = {};
childItem['item'] = children[j];
const { startDegree, middleDegree, endDegree } = getDegreeForIndexBasedOnSplitAngle(j, data);
childItem['startDegree'] = startDegree;
childItem['middleDegree'] = middleDegree;
childItem['endDegree'] = endDegree;
childItems.push(childItem);
}
return childItems;
}
function positionsOnAngles(centerX, centerY, radius, angels) {
const results = [];
for (let i = 0; i < angels.length; i++) {
const degreeToBeDrawn = angels[i];
results.push(polarToCartesian(centerX, centerY, radius, degreeToBeDrawn));
}
return results;
}
function getPointsOnCircleAtAngels(centerX, centerY, radius, angels) {
const results = [];
for (let i = 0; i < angels.length; i++) {
const degreeToBeDrawn = angels[i];
results.push(polarToCartesian(centerX, centerY, radius, degreeToBeDrawn));
}
return results;
}
function createSvgHandlerWithSelector(selectorName, center) {
const svg = document.querySelector(selectorName);
const pt = svg.createSVGPoint();
const maxRad = 6.283185307179586;
const maxDeg = 360;
return {
initPoint: 0,
cursorPoint(evt) {
const { x, y } = getCurrentPointFromEvent(evt);
pt.x = x;
pt.y = y;
return pt.matrixTransform(svg.getScreenCTM().inverse());
},
startDrag(evt, initialPoint) {
this.previousPoint = this.angleFromCenter(evt);
this.initPoint = initialPoint;
},
updateInitPointFromCurrentPoint(currentPoint) {
const difference = Math.abs(this.previousPoint - currentPoint);
if (currentPoint < this.previousPoint) {
this.initPoint += difference;
}
else {
this.initPoint -= difference;
}
},
getRotationAngle(evt) {
const currentPoint = this.angleFromCenter(evt);
this.updateInitPointFromCurrentPoint(currentPoint);
this.previousPoint = currentPoint;
return this.initPoint;
},
angleFromCenter(evt) {
const [centerX, centerY] = [center.x, center.y];
const { x, y } = this.cursorPoint(evt);
const angleInRadian = calculateAngleRadian({ x, y, centerX, centerY, maxRad });
const currentDegree = maxDeg * angleInRadian / (2 * Math.PI);
return currentDegree;
}
};
}
let AngularSunburstRadarChartComponent = class AngularSunburstRadarChartComponent {
constructor() {
this.componentDisplayed = false;
this.showToolTip = false;
this.tooltipTopInPx = '0px';
this.tooltipLeftInPx = '0px';
this.tooltipText = '';
this.svgId = null;
this.svgGroupId = null;
this.svgHandler = null;
this.currentRotationAngle = 10;
this.svgCursor = 'default';
this.initialized = false;
this.innerBorderHeight = 30;
this.outerBorderHeight = 30;
this.elements = [];
this.hasChildren = false;
this.outerBorderCircleRef = 'outerBorderCircle';
this.error = null;
this.hasError = false;
this.startRotation = false;
}
showError(msg) {
this.error = msg;
this.hasError = true;
}
hideError() {
this.error = null;
this.hasError = false;
}
appendToSvg(element) {
this.elements.push(element);
}
ngOnChanges(changes) {
this.hideError();
const isFirstChange = Object.values(changes).some(c => c.isFirstChange());
this.modifyOnFirstChange(isFirstChange);
}
modifyOnFirstChange(isFirstChange) {
if (!isFirstChange) {
this.initialize();
}
}
ngOnInit() {
this.hideError();
this.initialize();
}
initialize() {
const defaults = { size: 300, maxScore: 100, animateChart: true, splitBasedOnChildren: true, legendAxisLinePosition: 1 };
const options = Object.assign(Object.assign({}, defaults), (getOptionsOrEmpty(this.options)));
this.size = options.size;
this.maxScore = options.maxScore;
this.animateChart = options.animateChart;
this.splitBasedOnChildren = options.splitBasedOnChildren;
this.legendAxisLinePosition = options.legendAxisLinePosition;
if (!this.hasValidParameters()) {
this.showError('Input Values not set or Items was improper');
return;
}
this.initialized = false;
this.svgId = 'svg' + hashCode(this.items);
this.svgGroupId = 'svg-group' + hashCode(this.items);
this.viewBox = '0 0 ' + this.size + ' ' + this.size;
this.innerCircleRadius = Math.abs(this.size / 5.33);
this.innerBorderHeight = this.innerCircleRadius / 5;
this.outerBorderHeight = this.innerCircleRadius / 5;
this.globalPosition = getGlobalPositions({
size: this.size,
maxScore: this.maxScore,
items: this.items
});
this.hasChildren = this.items.filter(item => !!item.children).length > 0;
this.drawLayout();
const { innerRadius, innerTextRadius, textSize, outerTextSize, outerRadiusBorder, outerTextRadius, center } = this.globalPosition;
const [centerX, centerY] = [center.x, center.y];
let items = this.items;
let allAngels = [];
const hasChildren = this.hasChildren;
if (this.splitBasedOnChildren) {
// Cannot have no children
items = formatItems(items);
allAngels = getAllAnglesBasedOnChild(items);
}
else {
allAngels = getAllAnglesBasedOnParent(items);
}
const angleDifference = 360 / items.length;
const angles = allAngels.map(value => value.startDegree);
const middleAngles = allAngels.map(value => value.middleDegree);
const points = positionsOnAngles(centerX, centerY, this.chartBorder, angles);
const lines = [];
let elements = [];
let nextLevelElements = [];
for (let i = 0; i < points.length; i++) {
const { x, y } = points[i];
let endAngleIndex = i + 1;
if (endAngleIndex >= points.length) {
endAngleIndex = 0;
}
const endAngle = angles[endAngleIndex];
const startAngle = angles[i];
const middleAngle = middleAngles[i];
let item = items[i];
if (!!item.color === false) {
const colorDefaults = {
color: generateRandomColor()
};
item = Object.assign(Object.assign({}, colorDefaults), item);
}
if (this.hasChildren && item.children && item.children.length > 0) {
const childAngels = allAngels[i].children;
nextLevelElements = nextLevelElements.concat(this.drawOnLevel({
items: item.children,
totalDegrees: angleDifference,
childAngels,
color: item.color
}));
}
lines.push(createLine({ x1: centerX, y1: centerY, x2: x, y2: y, width: 2 }));
// Create Arc for inner values
const barWithinArc = createInnerChartBarWithInArc({
startPoint: center,
item,
radius: innerRadius,
startAngle,
endAngle,
maxScore: this.maxScore
});
elements.push(barWithinArc);
const innerTextElements = this.addArcText({
arcForTextId: 'arc-text-inner' + this.getUniqueCode() + '-' + i,
radius: innerTextRadius,
fontSize: textSize,
startAngle,
endAngle,
perAngle: angleDifference,
item
});
elements = elements.concat(innerTextElements);
if (hasChildren) {
elements.push(this.drawOuterBackgroundWithMiddle({ item, startAngle, middleAngle, endAngle }));
const outerBackgroundTextElements = this.addArcText({
arcForTextId: 'arc-text-outer' + this.getUniqueCode() + '-' + i,
radius: outerTextRadius,
fontSize: outerTextSize,
perAngle: angleDifference,
startAngle,
endAngle,
item
});
elements = elements.concat(outerBackgroundTextElements);
}
}
nextLevelElements.forEach(line => {
this.appendToSvg(line);
});
this.drawInnerBorders();
elements.forEach(line => {
this.appendToSvg(line);
});
lines.forEach(line => {
this.appendToSvg(line);
});
const legendAxisIndex = this.getLegendAxisIndex(angles);
this.drawLegends(angles[legendAxisIndex]);
this.addSmallCirclesAtCenter(centerX, centerY);
this.initialized = true;
this.currentRotationAngle = 10;
this.rotationPoint = getFormattedAngle(this.currentRotationAngle, center);
}
getLegendAxisIndex(angles) {
let legendAxisIndex = this.legendAxisLinePosition - 1;
if (legendAxisIndex < 0 || legendAxisIndex >= angles.length) {
legendAxisIndex = 0;
}
return legendAxisIndex;
}
hasValidParameters() {
return this.items && this.items.length > 1;
}
drawOuterBackgroundWithMiddle({ item, startAngle, middleAngle, endAngle }) {
const color = item.color;
const { outerTextRadius, center } = this.globalPosition;
const [centerX, centerY] = [center.x, center.y];
const middleCircle = calculatePointBetween({ centerX, centerY, startAngle, middleAngle, endAngle, radius: outerTextRadius });
const d = [
'M', middleCircle.start.x, middleCircle.start.y,
'A', outerTextRadius, outerTextRadius, 0, 0, 1, middleCircle.end.x, middleCircle.end.y
];
const title = item.name + '-' + item.value;
const strokeWidth = 0.0775 * this.size;
return createPath({ d: d.join(' '), stroke: color, 'stroke-width': strokeWidth, title });
}
drawOnLevel({ items, totalDegrees, childAngels, color }) {
const { innerRadiusBorder, middleRadius, textSize, middleTextRadius, center } = this.globalPosition;
const [centerX, centerY] = [center.x, center.y];
const angles = childAngels.map(item => item.startDegree);
const middleAngles = childAngels.map(item => item.middleDegree);
const endAngles = childAngels.map(item => item.endDegree);
const perAngle = totalDegrees / items.length;
const pointsOnInnerRadiusBorder = getPointsOnCircleAtAngels(centerX, centerY, innerRadiusBorder, angles);
const pointsOnMiddle = getPointsOnCircleAtAngels(centerX, centerY, middleRadius, angles);
let elements = [];
const lines = [];
let currentDegree = angles[0];
for (let i = 0; i < pointsOnInnerRadiusBorder.length; i++) {
const pointOnInnerRadiusBorder = pointsOnInnerRadiusBorder[i];
const pointOnMiddle = pointsOnMiddle[i];
lines.push(createLine({
x1: pointOnInnerRadiusBorder.x, y1: pointOnInnerRadiusBorder.y,
x2: pointOnMiddle.x, y2: pointOnMiddle.y, width: 0.5
}));
const startAngle = angles[i];
const middleAngle = middleAngles[i];
const endAngle = endAngles[i];
const item = items[i];
const params = {
startPoint: pointOnInnerRadiusBorder,
item,
startAngle,
endAngle,
middleAngle,
middleRadius,
innerRadiusBorder,
center,
maxScore: this.maxScore,
color,
index: i
};
const arcForChart = createOuterChartBarWithInArc(params);
elements.push(arcForChart);
const middleTextElements = this.addArcText({
arcForTextId: 'arc-text-middle' + this.getUniqueCode() + '-' + startAngle + i,
radius: middleTextRadius,
fontSize: textSize,
perAngle,
startAngle,
endAngle,
item
});
elements = elements.concat(middleTextElements);
currentDegree = currentDegree + perAngle;
}
lines.forEach(line => {
elements.push(line);
});
return elements;
}
getUniqueCode() {
return hashCode(this.items) + '-' + this.size;
}
drawInnerBorders() {
const { innerRadius, innerRadiusBorder, center } = this.globalPosition;
const [centerX, centerY] = [center.x, center.y];
const innerContainer = createCircle({
x: centerX,
y: centerY,
radius: innerRadius,
fillColor: 'none'
});
const innerBorderContainer = createCircle({
x: centerX,
y: centerY,
radius: innerRadiusBorder,
fillColor: '#FFFFFF'
});
this.appendToSvg(innerBorderContainer);
this.appendToSvg(innerContainer);
}
addSmallCirclesAtCenter(centerX, centerY) {
const outerRadius = 0.0025 * 2 * this.size;
const innerRadius = 0.0005 * 2 * this.size;
this.appendToSvg(createCircle({
x: centerX,
y: centerY,
radius: outerRadius,
fillColor: '#FFFFFF'
}));
this.appendToSvg(createCircle({
x: centerX,
y: centerY,
radius: innerRadius,
fillColor: '#FFFFFF'
}));
}
drawLegends(degreeToBeDrawn) {
const { innerRadius, innerRadiusBorder, middleRadius, center } = this.globalPosition;
const [centerX, centerY] = [center.x, center.y];
const maxScore = this.maxScore;
let legends = createLegends({ startPoint: center, radius: innerRadius, degreeToBeDrawn, maxScore });
const startFrom = polarToCartesian(centerX, centerY, innerRadiusBorder, degreeToBeDrawn);
if (this.hasChildren) {
const nextLevelLegends = createLegendWithOptions({
startPoint: startFrom,
center,
startRadius: innerRadiusBorder,
endRadius: middleRadius,
maxScore,
degreeToBeDrawn
});
legends = legends.concat(nextLevelLegends);
}
legends.forEach(elem => {
this.appendToSvg(elem);
});
}
addArcText({ arcForTextId, radius, startAngle, fontSize, endAngle, perAngle, item }) {
const elements = [];
const { center } = this.globalPosition;
const [centerX, centerY] = [center.x, center.y];
const arcForText = createArcToWriteText({ id: arcForTextId, startPoint: center, radius, startAngle, endAngle });
elements.push(arcForText);
const distance = distanceBetweenTwoPoints(centerX, centerY, radius, startAngle, endAngle);
const label = getTextForAngle(item.name, distance, fontSize);
elements.push(writeTextOnArc({ label, text: item.name, pathId: arcForTextId, 'font-size': fontSize + 'px' }));
return elements;
}
drawLayout() {
const { innerRadius, innerRadiusBorder, middleRadius, middleRadiusBorder, outerRadius, outerRadiusBorder, center } = this.globalPosition;
const [centerX, centerY] = [center.x, center.y];
let innerRadiusEnd = innerRadius;
let innerRadiusBorderEnd = innerRadiusBorder;
this.chartBorder = innerRadiusBorder;
if (this.hasChildren) {
const outerBorderCircle = createCircle({
x: centerX,
y: centerY,
radius: outerRadiusBorder,
fillColor: 'none',
'stroke-width': '5',
ref: this.outerBorderCircleRef
});
this.appendToSvg(outerBorderCircle);
const outerCircle = createCircle({
x: centerX,
y: centerY,
radius: outerRadius,
fillColor: 'none'
});
this.appendToSvg(outerCircle);
innerRadiusEnd = middleRadius;
innerRadiusBorderEnd = middleRadiusBorder;
this.chartBorder = outerRadiusBorder;
}
this.appendToSvg(createCircle({ x: centerX, y: centerY, radius: innerRadiusEnd }));
this.appendToSvg(createCircle({ x: centerX, y: centerY, radius: innerRadiusBorderEnd }));
}
hideTooltip() {
this.showToolTip = false;
}
showTooltipText($event, text) {
this.tooltipLeftInPx = $event.pageX + 10 + 'px';
this.tooltipTopInPx = $event.pageY + 10 + 'px';
this.tooltipText = text;
this.showToolTip = true;
}
onOutOfComponent() {
this.hideTooltip();
this.stopRotate();
}
stopRotate() {
this.svgCursor = 'default';
this.startRotation = false;
}
startRotate($event) {
this.startRotation = true;
this.svgCursor = 'grab';
const { outerRadiusBorder, center } = this.globalPosition;
this.svgHandler = createSvgHandlerWithSelector('#' + this.svgId, center);
this.svgHandler.startDrag($event, this.currentRotationAngle);
$event.preventDefault();
}
rotateChart($event) {
if (this.startRotation == false) {
return;
}
this.svgCursor = 'grabbing';
const { center } = this.globalPosition;
this.currentRotationAngle = this.svgHandler.getRotationAngle($event);
this.rotationPoint = getFormattedAngle(Math.round(this.currentRotationAngle), center);
}
};
__decorate([
Input()
], AngularSunburstRadarChartComponent.prototype, "items", void 0);
__decorate([
Input()
], AngularSunburstRadarChartComponent.prototype, "options", void 0);
AngularSunburstRadarChartComponent = __decorate([
Component({
selector: 'lib-sunburst-radar-chart',
template: "<ng-container (mouseout)=\"onOutOfComponent();\">\n\n\n <div *ngIf=\"hasError\" style=\" color: #a94442;\n background-color: #f2dede;\n border-color: #ebccd1;\n margin-top: 5px;\npadding: 15px;\n margin-bottom: 20px;\n border: 1px solid transparent;\n border-radius: 4px;\n\">\n <strong>Error!</strong> {{error}}\n </div>\n\n\n\n<div class=\"app-tooltip\" *ngIf=\"showToolTip\" style=\"position: absolute; display: block;background: cornsilk; border: 1px solid black; border-radius: 5px; padding: 5px; z-index: 1002;\"\n\n [style.left]=\"tooltipLeftInPx\" [style.top]=\"tooltipTopInPx\" >\n {{tooltipText}}\n</div>\n\n<svg [attr.id]=\"svgId\"\n [attr.height]=\"size\"\n [attr.width]=\"size\"\n [attr.viewBox]=\"viewBox\"\n [style.cursor]=\"svgCursor\"\n xmlns=\"http://www.w3.org/2000/svg\">\n\n\n\n\n <animateTransform\n *ngIf=\"animateChart\"\n attributeName=\"transform\"\n begin=\"4s\"\n dur=\"500ms\"\n type=\"rotate\"\n from=\"0\"\n to=\"10\"\n\n additive=\"sum\"\n fill=\"freeze\"\n repeatCount=\"1\"\n />\n\n\n<g\n (mousemove)=\"rotateChart($event);\"\n (mousedown)=\"startRotate($event)\"\n (mouseup)=\"stopRotate()\"\n (touchmove)=\"rotateChart($event);\"\n (touchstart)=\"startRotate($event)\"\n (touchend)=\"stopRotate()\"\n [attr.transform]=\"rotationPoint\"\n [attr.id]=\"svgGroupId\"\n\n\n >\n <ng-container *ngFor=\"let element of elements\" >\n\n\n\n <ng-container [ngSwitch]=\"element.name\" >\n\n\n <circle *ngSwitchCase=\"'circle'\" [attr.cx]=\"element.options.x\" [attr.cy]=[element.options.y]\n [attr.r]=\"element.options.radius\"\n [attr.stroke-width]=\"element.options['stroke-width']\"\n [attr.stroke]=\"element.options['stroke']\"\n [attr.stroke-dasharray]=\"element.options['stroke-dasharray']\"\n [attr.stroke-opacity]=\"element.options['stroke-opacity']\"\n [attr.fill]=\"element.options['fillColor']\"\n >\n\n\n\n\n </circle>\n\n <path *ngSwitchCase=\"'path'\" [attr.d]=\"element.options.d\" [attr.fill]=[element.options.fill]\n\n [attr.stroke]=\"element.options['stroke']\"\n [attr.stroke-width]=\"element.options['stroke-width']\"\n [attr.id]=\"element.options['id']\"\n (mousemove)=\"showTooltipText($event, element.options['title']);\"\n (mouseout)=\"hideTooltip()\" >\n\n\n\n\n\n </path>\n\n<g *ngSwitchCase=\"'path-bar'\" >\n\n\n\n <path [attr.d]=\"element.options.d\"\n [attr.fill]=[element.options.fill]\n\n [attr.stroke]=\"element.options['stroke']\"\n [attr.stroke-width]=\"element.options['stroke-width']\"\n [attr.stroke-opacity]=\"element.options['stroke-opacity']\"\n [attr.fill-opacity]=\"element.options['fill-opacity']\"\n [attr.id]=\"element.options['id']\"\n (mousemove)=\"showTooltipText($event, element.options['title']);\" (mouseout)=\"hideTooltip()\" >\n\n\n\n\n <ng-container\n\n *ngIf=\"animateChart\"\n >\n\n <animate\n attributeName=\"fill\"\n [attr.from]=\"element.options['fill']\"\n to=\"transparent\"\n dur=\"1ms\"\n fill=\"freeze\" />\n <animate\n attributeName=\"stroke\"\n [attr.from]=\"element.options['fill']\"\n [attr.to]=\"element.options['fill']\"\n dur=\"1ms\"\n fill=\"freeze\" />\n <animate\n attributeName=\"stroke-width\"\n from=\"8\"\n to=\"8\"\n dur=\"1ms\"\n fill=\"freeze\" />\n <animate\n attributeName=\"stroke-dasharray\"\n from=\"1000\"\n to=\"1000\"\n dur=\"1ms\"\n fill=\"freeze\" />\n\n\n <animate\n attributeName=\"fill\"\n from=\"#FFFFFF\"\n [attr.to]=\"element.options['fill']\"\n begin=\"2s\"\n dur=\"3s\"\n fill=\"freeze\" />\n\n <animate\n attributeName=\"stroke-dashoffset\"\n from=\"1000\"\n to=\"0\"\n begin=\"1ms\"\n dur=\"3s\"\n fill=\"freeze\" />\n\n <animate\n attributeName=\"stroke-width\"\n from=\"8\"\n [attr.to]=\"element.options['stroke']\"\n begin=\"3s\"\n dur=\"1s\"\n fill=\"freeze\" />\n </ng-container>\n </path>\n\n\n\n\n</g>\n\n <text *ngSwitchCase=\"'text-on-arc'\"\n [attr.font-size]=\"element.options['font-size']\"\n\n >\n <textPath\n [attr.href]=\"element.options.href\"\n [attr.startOffset]=\"element.options.startOffset\"\n [attr.text-anchor]=\"element.options['text-anchor']\"\n >{{element.options['label']}}</textPath>\n <title>{{element.options['title']}}</title>\n\n </text>\n\n\n <line *ngSwitchCase=\"'line'\"\n [attr.x1]=\"element.options.x1\"\n [attr.y1]=\"element.options.y1\"\n [attr.x2]=\"element.options.x2\"\n [attr.y2]=\"element.options.y2\"\n [attr.stroke-width]=\"element.options.width\"\n\n [attr.stroke]=\"element.options['color']\" >\n <title>{{element.options['title']}}</title>\n </line>\n\n\n <text *ngSwitchCase=\"'text'\"\n [attr.x]=\"element.options.x\"\n [attr.y]=\"element.options.y\"\n [attr.stroke]=\"element.options.stroke\"\n [attr.stroke-width]=\"element.options['stroke-width']\"\n [attr.font-size]=\"element.options['font-size']\"\n [attr.text-anchor]=\"element.options['text-anchor']\"\n >{{element.options.content}}\n <title>{{element.options['content']}}</title>\n </text>\n\n </ng-container>\n\n\n\n </ng-container>\n\n\n </g>\n</svg>\n</ng-container>\n",
styles: [".app-tooltip{background:#fff8dc;border:1px solid #000;border-radius:5px;padding:5px;z-index:1002}.growAnimation{-moz-transition:transform 2s ease-in-out;-webkit-transition:transform 2s ease-in-out;-o-transition:transform 2s ease-in-out;transform:scale(1)}.growAnimation:active{transform:scale(.5)}"]
})
], AngularSunburstRadarChartComponent);
let AngularSunburstRadarChartModule = class AngularSunburstRadarChartModule {
};
AngularSunburstRadarChartModule = __decorate([
NgModule({
declarations: [AngularSunburstRadarChartComponent],
imports: [
CommonModule
],
exports: [AngularSunburstRadarChartComponent]
})
], AngularSunburstRadarChartModule);
/*
* Public API Surface of angular-sunburst-radar-chart
*/
/**
* Generated bundle index. Do not edit.
*/
export { AngularSunburstRadarChartComponent, AngularSunburstRadarChartModule };
//# sourceMappingURL=angular-sunburst-radar-chart.js.map