UNPKG

veritatisquam

Version:
327 lines (287 loc) 9.16 kB
// Internal Imports import { Component } from '../component'; import { DOMUtils } from '../../services'; import { Tools } from '../../tools'; import { Skeletons, CartesianOrientations, Alignments, } from '../../interfaces/enums'; import * as Configuration from '../../configuration'; // D3 Imports import { scaleLinear } from 'd3-scale'; import { easeLinear } from 'd3-ease'; import { arc } from 'd3-shape'; export class Skeleton extends Component { type = 'skeleton'; xScale: any; yScale: any; backdrop: any; render() { const svg = this.parent; const parent = svg.node().parentNode; const { width, height } = DOMUtils.getSVGElementSize(parent, { useAttrs: true, }); svg.attr('width', width).attr('height', height); const isDataLoading = Tools.getProperty( this.getOptions(), 'data', 'loading' ); // display a skeleton if there is no chart data or the loading flag is set to true if (isDataLoading) { this.renderSkeleton(isDataLoading); } else { this.removeSkeleton(); } } renderSkeleton(showShimmerEffect: boolean) { if (this.configs.skeleton === Skeletons.GRID) { this.renderGridSkeleton(showShimmerEffect); } else if (this.configs.skeleton === Skeletons.VERT_OR_HORIZ) { this.renderVertOrHorizSkeleton(showShimmerEffect); } else if (this.configs.skeleton === Skeletons.PIE) { this.renderPieSkeleton(showShimmerEffect); } else if (this.configs.skeleton === Skeletons.DONUT) { this.renderDonutSkeleton(showShimmerEffect); } else { throw new Error( `"${this.configs.skeleton}" is not a valid Skeleton type.` ); } } renderGridSkeleton(showShimmerEffect: boolean) { this.setScales(); this.drawBackdrop(showShimmerEffect); this.drawXGrid(showShimmerEffect); this.drawYGrid(showShimmerEffect); if (showShimmerEffect) { this.setShimmerEffect('shimmer-lines'); } } renderVertOrHorizSkeleton(showShimmerEffect: boolean) { const orientation = this.services.cartesianScales.getOrientation(); this.setScales(); this.drawBackdrop(showShimmerEffect); if (orientation === CartesianOrientations.VERTICAL) { this.drawYGrid(showShimmerEffect); } if (orientation === CartesianOrientations.HORIZONTAL) { this.drawXGrid(showShimmerEffect); } this.setShimmerEffect('shimmer-lines'); } renderPieSkeleton(showShimmerEffect: boolean) { const outerRadius = this.computeOuterRadius(); const innerRadius = 0; this.drawRing(outerRadius, innerRadius, showShimmerEffect); if (showShimmerEffect) { this.setShimmerEffect('shimmer-areas'); } } renderDonutSkeleton(showShimmerEffect: boolean) { const outerRadius = this.computeOuterRadius(); const innerRadius = this.computeInnerRadius(); this.drawRing(outerRadius, innerRadius, showShimmerEffect); if (showShimmerEffect) { this.setShimmerEffect('shimmer-areas'); } } setScales() { const xRange = this.services.cartesianScales.getMainXScale().range(); const yRange = this.services.cartesianScales.getMainYScale().range(); this.xScale = scaleLinear().domain([0, 1]).range(xRange); this.yScale = scaleLinear().domain([0, 1]).range(yRange); } drawBackdrop(showShimmerEffect: boolean) { const svg = this.parent; const parent = svg.node().parentNode; const { width, height } = DOMUtils.getSVGElementSize(parent, { useAttrs: true, }); this.backdrop = DOMUtils.appendOrSelect(svg, 'svg.chart-skeleton.DAII') .attr('width', width) .attr('height', height); const backdropRect = DOMUtils.appendOrSelect( this.backdrop, 'rect.chart-skeleton-backdrop' ); backdropRect.attr('width', '100%').attr('height', '100%'); const [xScaleStart, xScaleEnd] = this.xScale.range(); const [yScaleEnd, yScaleStart] = this.yScale.range(); this.backdrop .merge(backdropRect) .attr('x', xScaleStart) .attr('y', yScaleStart); backdropRect .classed('shimmer-effect-lines', showShimmerEffect) .classed('empty-state-lines', !showShimmerEffect); } drawXGrid(showShimmerEffect: boolean) { const height = this.backdrop.attr('height'); const width = this.backdrop.attr('width'); const ticksNumber = Tools.getProperty( this.getOptions(), 'grid', 'x', 'numberOfTicks' ); const ticksValues = this.xScale .ticks(ticksNumber) .map((d) => d * width); const xGridG = DOMUtils.appendOrSelect(this.backdrop, 'g.x.skeleton'); const update = xGridG.selectAll('line').data(ticksValues); update .enter() .append('line') .merge(update) .attr('x1', (d) => d) .attr('x2', (d) => d) .attr('y1', 0) .attr('y2', height); xGridG .selectAll('line') .classed('shimmer-effect-lines', showShimmerEffect) .classed('empty-state-lines', !showShimmerEffect); } drawYGrid(showShimmerEffect: boolean) { const height = this.backdrop.attr('height'); const width = this.backdrop.attr('width'); const ticksNumber = Tools.getProperty( this.getOptions(), 'grid', 'y', 'numberOfTicks' ); const ticksValues = this.xScale .ticks(ticksNumber) .map((d) => d * height); const yGridG = DOMUtils.appendOrSelect(this.backdrop, 'g.y.skeleton'); const update = yGridG.selectAll('line').data(ticksValues); update .enter() .append('line') .merge(update) .attr('x1', 0) .attr('x2', width) .attr('y1', (d) => d) .attr('y2', (d) => d); yGridG .selectAll('line') .classed('shimmer-effect-lines', showShimmerEffect) .classed('empty-state-lines', !showShimmerEffect); } drawRing(outerRadius: number, innerRadius: number, shimmer = true) { const svg = this.parent; const { width, height } = DOMUtils.getSVGElementSize( svg.node().parentNode, { useAttrs: true } ); const container = DOMUtils.appendOrSelect(svg, 'svg.chart-skeleton') .attr('width', width) .attr('height', height); const optionName = innerRadius === 0 ? 'pie' : 'donut'; const alignment = Tools.getProperty( this.getOptions(), optionName, 'alignment' ); DOMUtils.appendOrSelect(container, 'rect.chart-skeleton-area-container') .attr('width', width) .attr('height', height) .attr('fill', 'none'); const arcPathGenerator = arc() .innerRadius(innerRadius) .outerRadius(outerRadius) .startAngle(0) .endAngle(Math.PI * 2); // centering circle inside the container const tcx = outerRadius + Math.abs(Configuration.pie.radiusOffset); const tcy = outerRadius + (Math.min(width, height) - outerRadius * 2) / 2; const skeletonAreaShape = DOMUtils.appendOrSelect(container, 'path') .attr('class', 'skeleton-area-shape') .attr('transform', `translate(${tcx}, ${tcy})`) .attr('d', arcPathGenerator) .classed('shimmer-effect-areas', shimmer) .classed('empty-state-areas', !shimmer); // Position skeleton let translateX = outerRadius + Configuration.pie.xOffset; if (alignment === Alignments.CENTER) { translateX = width / 2; } else if (alignment === Alignments.RIGHT) { translateX = width - outerRadius - Configuration.pie.xOffset; } const translateY = outerRadius + Configuration.pie.yOffset; skeletonAreaShape.attr( 'transform', `translate(${translateX}, ${translateY})` ); } // same logic in pie computeOuterRadius() { const { width, height } = DOMUtils.getSVGElementSize( this.parent.node().parentNode, { useAttrs: true } ); const radius = Math.min(width, height) / 2; return radius + Configuration.pie.radiusOffset; } // same logic in donut computeInnerRadius() { return this.computeOuterRadius() * (3 / 4); } setShimmerEffect(gradientId: string) { const animationDuration = 2000; // ms const delay = 1000; // ms const shimmerWidth = 0.2; const stopBgShimmerClass = 'stop-bg-shimmer'; const stopShimmerClass = 'stop-shimmer'; const container = this.parent.select('.chart-skeleton'); const { width } = DOMUtils.getSVGElementSize(this.parent, { useAttrs: true, }); const startPoint = 0; const endPoint = width; // append the defs as first child of container const defs = DOMUtils.appendOrSelect(container, 'defs').lower(); const linearGradient = DOMUtils.appendOrSelect(defs, 'linearGradient') .attr('id', gradientId) .attr('x1', startPoint - 3 * shimmerWidth * width) .attr('x2', endPoint) .attr('y1', 0) .attr('y2', 0) .attr('gradientUnits', 'userSpaceOnUse') .attr('gradientTransform', `translate(0, 0)`); const stops = ` <stop class="${stopBgShimmerClass}" offset="${startPoint}"></stop> <stop class="${stopShimmerClass}" offset="${startPoint + shimmerWidth}"></stop> <stop class="${stopBgShimmerClass}" offset="${ startPoint + 2 * shimmerWidth }"></stop> `; linearGradient.html(stops); repeat(); function repeat() { linearGradient .attr( 'gradientTransform', `translate(${startPoint - 3 * shimmerWidth * width}, 0)` ) .transition() .duration(animationDuration) .delay(delay) .ease(easeLinear) .attr( 'gradientTransform', `translate(${endPoint + 3 * shimmerWidth * width}, 0)` ) .on('end', repeat); } } removeSkeleton() { const container = this.parent.select('.chart-skeleton'); container.remove(); } }