将javascript嵌套函数迁移到typescript会出错

时间:2022-04-17 15:55:29

I try to reuse a javascript function d3.js liquid gauge which contains nested function in typescript but running into a lot of weird error.

我尝试重用一个javascript函数d3.js液体量表,它包含了打字稿中的嵌套函数,但遇到了很多奇怪的错误。

The javascript snippet is as below.

javascript片段如下。

function loadLiquidFillGauge(elementId, value, config) {
    if(config == null) config = liquidFillGaugeDefaultSettings();

    var gauge = d3.select("#" + elementId);
    var radius = Math.min(parseInt(gauge.style("width")), parseInt(gauge.style("height")))/2;
    var locationX = parseInt(gauge.style("width"))/2 - radius;
    var locationY = parseInt(gauge.style("height"))/2 - radius;
    var fillPercent = Math.max(config.minValue, Math.min(config.maxValue, value))/config.maxValue;

    var waveHeightScale;
    if(config.waveHeightScaling){
        waveHeightScale = d3.scale.linear()
            .range([0,config.waveHeight,0])
            .domain([0,50,100]);
    } else {
        waveHeightScale = d3.scale.linear()
            .range([config.waveHeight,config.waveHeight])
            .domain([0,100]);
    }

    var textPixels = (config.textSize*radius/2);
    var textFinalValue = parseFloat(value).toFixed(2);
    var textStartValue = config.valueCountUp?config.minValue:textFinalValue;
    var percentText = config.displayPercent?"%":"";
    var circleThickness = config.circleThickness * radius;
    var circleFillGap = config.circleFillGap * radius;
    var fillCircleMargin = circleThickness + circleFillGap;
    var fillCircleRadius = radius - fillCircleMargin;
    var waveHeight = fillCircleRadius*waveHeightScale(fillPercent*100);

    var waveLength = fillCircleRadius*2/config.waveCount;
    var waveClipCount = 1+config.waveCount;
    var waveClipWidth = waveLength*waveClipCount;

    // Rounding functions so that the correct number of decimal places is always displayed as the value counts up.
    var textRounder = function(value){ return Math.round(value); };
    if(parseFloat(textFinalValue) != parseFloat(textRounder(textFinalValue))){
        textRounder = function(value){ return parseFloat(value).toFixed(1); };
    }
    if(parseFloat(textFinalValue) != parseFloat(textRounder(textFinalValue))){
        textRounder = function(value){ return parseFloat(value).toFixed(2); };
    }

    // Data for building the clip wave area.
    var data = [];
    for(var i = 0; i <= 40*waveClipCount; i++){
        data.push({x: i/(40*waveClipCount), y: (i/(40))});
    }

    // Scales for drawing the outer circle.
    var gaugeCircleX = d3.scale.linear().range([0,2*Math.PI]).domain([0,1]);
    var gaugeCircleY = d3.scale.linear().range([0,radius]).domain([0,radius]);

    // Scales for controlling the size of the clipping path.
    var waveScaleX = d3.scale.linear().range([0,waveClipWidth]).domain([0,1]);
    var waveScaleY = d3.scale.linear().range([0,waveHeight]).domain([0,1]);

    // Scales for controlling the position of the clipping path.
    var waveRiseScale = d3.scale.linear()
        // The clipping area size is the height of the fill circle + the wave height, so we position the clip wave
        // such that the it will overlap the fill circle at all when at 0%, and will totally cover the fill
        // circle at 100%.
        .range([(fillCircleMargin+fillCircleRadius*2+waveHeight),(fillCircleMargin-waveHeight)])
        .domain([0,1]);
    var waveAnimateScale = d3.scale.linear()
        .range([0, waveClipWidth-fillCircleRadius*2]) // Push the clip area one full wave then snap back.
        .domain([0,1]);

    // Scale for controlling the position of the text within the gauge.
    var textRiseScaleY = d3.scale.linear()
        .range([fillCircleMargin+fillCircleRadius*2,(fillCircleMargin+textPixels*0.7)])
        .domain([0,1]);

    // Center the gauge within the parent SVG.
    var gaugeGroup = gauge.append("g")
        .attr('transform','translate('+locationX+','+locationY+')');

    // Draw the outer circle.
    var gaugeCircleArc = d3.svg.arc()
        .startAngle(gaugeCircleX(0))
        .endAngle(gaugeCircleX(1))
        .outerRadius(gaugeCircleY(radius))
        .innerRadius(gaugeCircleY(radius-circleThickness));
    gaugeGroup.append("path")
        .attr("d", gaugeCircleArc)
        .style("fill", config.circleColor)
        .attr('transform','translate('+radius+','+radius+')');

    // Text where the wave does not overlap.
    var text1 = gaugeGroup.append("text")
        .text(textRounder(textStartValue) + percentText)
        .attr("class", "liquidFillGaugeText")
        .attr("text-anchor", "middle")
        .attr("font-size", textPixels + "px")
        .style("fill", config.textColor)
        .attr('transform','translate('+radius+','+textRiseScaleY(config.textVertPosition)+')');

    // The clipping wave area.
    var clipArea = d3.svg.area()
        .x(function(d) { return waveScaleX(d.x); } )
        .y0(function(d) { return waveScaleY(Math.sin(Math.PI*2*config.waveOffset*-1 + Math.PI*2*(1-config.waveCount) + d.y*2*Math.PI));} )
        .y1(function(d) { return (fillCircleRadius*2 + waveHeight); } );
    var waveGroup = gaugeGroup.append("defs")
        .append("clipPath")
        .attr("id", "clipWave" + elementId);
    var wave = waveGroup.append("path")
        .datum(data)
        .attr("d", clipArea)
        .attr("T", 0);

    // The inner circle with the clipping wave attached.
    var fillCircleGroup = gaugeGroup.append("g")
        .attr("clip-path", "url(#clipWave" + elementId + ")");
    fillCircleGroup.append("circle")
        .attr("cx", radius)
        .attr("cy", radius)
        .attr("r", fillCircleRadius)
        .style("fill", config.waveColor);

    // Text where the wave does overlap.
    var text2 = fillCircleGroup.append("text")
        .text(textRounder(textStartValue) + percentText)
        .attr("class", "liquidFillGaugeText")
        .attr("text-anchor", "middle")
        .attr("font-size", textPixels + "px")
        .style("fill", config.waveTextColor)
        .attr('transform','translate('+radius+','+textRiseScaleY(config.textVertPosition)+')');

    // Make the value count up.
    if(config.valueCountUp){
        var textTween = function(){
            var i = d3.interpolate(this.textContent, textFinalValue);
            return function(t) { this.textContent = textRounder(i(t)) + percentText; }
        };
        text1.transition()
            .duration(config.waveRiseTime)
            .tween("text", textTween);
        text2.transition()
            .duration(config.waveRiseTime)
            .tween("text", textTween);
    }

    // Make the wave rise. wave and waveGroup are separate so that horizontal and vertical movement can be controlled independently.
    var waveGroupXPosition = fillCircleMargin+fillCircleRadius*2-waveClipWidth;
    if(config.waveRise){
        waveGroup.attr('transform','translate('+waveGroupXPosition+','+waveRiseScale(0)+')')
            .transition()
            .duration(config.waveRiseTime)
            .attr('transform','translate('+waveGroupXPosition+','+waveRiseScale(fillPercent)+')')
            .each("start", function(){ wave.attr('transform','translate(1,0)'); }); // This transform is necessary to get the clip wave positioned correctly when waveRise=true and waveAnimate=false. The wave will not position correctly without this, but it's not clear why this is actually necessary.
    } else {
        waveGroup.attr('transform','translate('+waveGroupXPosition+','+waveRiseScale(fillPercent)+')');
    }

    if(config.waveAnimate) animateWave();

    function animateWave() {
        wave.attr('transform','translate('+waveAnimateScale(wave.attr('T'))+',0)');
        wave.transition()
            .duration(config.waveAnimateTime * (1-wave.attr('T')))
            .ease('linear')
            .attr('transform','translate('+waveAnimateScale(1)+',0)')
            .attr('T', 1)
            .each('end', function(){
                wave.attr('T', 0);
                animateWave(config.waveAnimateTime);
            });
    }

}

I copied the above code to a typescript class, removed the function keyword and added some type restrictions like below.

我将上面的代码复制到typescript类,删除了function关键字并添加了一些类型限制,如下所示。

   liquidFillGaugeDefaultSettings(){
       return {
           minValue: 0, // The gauge minimum value.
           maxValue: 100, // The gauge maximum value.
           circleThickness: 0.05, // The outer circle thickness as a percentage of it's radius.
           circleFillGap: 0.05, // The size of the gap between the outer circle and wave circle as a percentage of the outer circles radius.
           circleColor: "#152935", // The color of the outer circle.
           waveHeight: 0.05, // The wave height as a percentage of the radius of the wave circle.
           waveCount: 1, // The number of full waves per width of the wave circle.
           waveRiseTime: 1000, // The amount of time in milliseconds for the wave to rise from 0 to it's final height.
           waveAnimateTime: 1000, // The amount of time in milliseconds for a full wave to enter the wave circle.
           waveRise: true, // Control if the wave should rise from 0 to it's full height, or start at it's full height.
           waveHeightScaling: true, // Controls wave size scaling at low and high fill percentages. When true, wave height reaches it's maximum at 50% fill, and minimum at 0% and 100% fill. This helps to prevent the wave from making the wave circle from appear totally full or empty when near it's minimum or maximum fill.
           waveAnimate: true, // Controls if the wave scrolls or is static.
           waveColor: "#41d6c3", // The color of the fill wave.
           waveOffset: 0, // The amount to initially offset the wave. 0 = no offset. 1 = offset of one full wave.
           textVertPosition: .5, // The height at which to display the percentage text withing the wave circle. 0 = bottom, 1 = top.
           textSize: 1, // The relative height of the text to display in the wave circle. 1 = 50%
           valueCountUp: true, // If true, the displayed value counts up from 0 to it's final value upon loading. If false, the final value is displayed.
           displayPercent: true, // If true, a % symbol is displayed after the value.
           textColor: "#045681", // The color of the value text when the wave does not overlap it.
           waveTextColor: "#A4DBf8" // The color of the value text when the wave overlaps it.
       };
   }

   loadLiquidFillGauge(elementId:string, value:string, config:any) {
       if(config == null) config = this.liquidFillGaugeDefaultSettings();

       var gauge = d3.select("#" + elementId);
       var radius = Math.min(parseInt(gauge.style("width")), parseInt(gauge.style("height")))/2;
       var locationX = parseInt(gauge.style("width"))/2 - radius;
       var locationY = parseInt(gauge.style("height"))/2 - radius;
       var fillPercent = Math.max(config.minValue, Math.min(config.maxValue, +value))/config.maxValue;

       var waveHeightScale;
       if(config.waveHeightScaling){
           waveHeightScale = d3.scale.linear()
               .range([0,config.waveHeight,0])
               .domain([0,50,100]);
       } else {
           waveHeightScale = d3.scale.linear()
               .range([config.waveHeight,config.waveHeight])
               .domain([0,100]);
       }

       var textPixels = (config.textSize*radius/2);
       var textFinalValue = parseFloat(value).toFixed(2);
       var textStartValue = config.valueCountUp?config.minValue:textFinalValue;
       var percentText = config.displayPercent?"%":"";
       var circleThickness = config.circleThickness * radius;
       var circleFillGap = config.circleFillGap * radius;
       var fillCircleMargin = circleThickness + circleFillGap;
       var fillCircleRadius = radius - fillCircleMargin;
       var waveHeight = fillCircleRadius*waveHeightScale(fillPercent*100);

       var waveLength = fillCircleRadius*2/config.waveCount;
       var waveClipCount = 1+config.waveCount;
       var waveClipWidth = waveLength*waveClipCount;

       // Rounding functions so that the correct number of decimal places is always displayed as the value counts up.
       var textRounder = function(value:string){ return Math.round(+value); };
       if(parseFloat(textFinalValue) != parseFloat(textRounder(textFinalValue))){
           textRounder = function(value:string){ return parseFloat(value).toFixed(1); };
       }
       if(parseFloat(textFinalValue) != parseFloat(textRounder(textFinalValue))){
           textRounder = function(value:string){ return parseFloat(value).toFixed(2); };
       }

       // Data for building the clip wave area.
       var data = [];
       for(var i = 0; i <= 40*waveClipCount; i++){
           data.push({x: i/(40*waveClipCount), y: (i/(40))});
       }

       // Scales for drawing the outer circle.
       var gaugeCircleX = d3.scale.linear().range([0,2*Math.PI]).domain([0,1]);
       var gaugeCircleY = d3.scale.linear().range([0,radius]).domain([0,radius]);

       // Scales for controlling the size of the clipping path.
       var waveScaleX = d3.scale.linear().range([0,waveClipWidth]).domain([0,1]);
       var waveScaleY = d3.scale.linear().range([0,waveHeight]).domain([0,1]);

       // Scales for controlling the position of the clipping path.
       var waveRiseScale = d3.scale.linear()
           // The clipping area size is the height of the fill circle + the wave height, so we position the clip wave
           // such that the it will overlap the fill circle at all when at 0%, and will totally cover the fill
           // circle at 100%.
           .range([(fillCircleMargin+fillCircleRadius*2+waveHeight),(fillCircleMargin-waveHeight)])
           .domain([0,1]);
       var waveAnimateScale = d3.scale.linear()
           .range([0, waveClipWidth-fillCircleRadius*2]) // Push the clip area one full wave then snap back.
           .domain([0,1]);

       // Scale for controlling the position of the text within the gauge.
       var textRiseScaleY = d3.scale.linear()
           .range([fillCircleMargin+fillCircleRadius*2,(fillCircleMargin+textPixels*0.7)])
           .domain([0,1]);

       // Center the gauge within the parent SVG.
       var gaugeGroup = gauge.append("g")
           .attr('transform','translate('+locationX+','+locationY+')');

       // Draw the outer circle.
       var gaugeCircleArc = d3.svg.arc()
           .startAngle(gaugeCircleX(0))
           .endAngle(gaugeCircleX(1))
           .outerRadius(gaugeCircleY(radius))
           .innerRadius(gaugeCircleY(radius-circleThickness));
       gaugeGroup.append("path")
           .attr("d", gaugeCircleArc)
           .style("fill", config.circleColor)
           .attr('transform','translate('+radius+','+radius+')');

       // Text where the wave does not overlap.
       var text1 = gaugeGroup.append("text")
           .text(textRounder(textStartValue) + percentText)
           .attr("class", "liquidFillGaugeText")
           .attr("text-anchor", "middle")
           .attr("font-size", textPixels + "px")
           .style("fill", config.textColor)
           .attr('transform','translate('+radius+','+textRiseScaleY(config.textVertPosition)+')');

       // The clipping wave area.
       var clipArea = d3.svg.area()
           .x(function(d) { return waveScaleX(d.x); } )
           .y0(function(d) { return waveScaleY(Math.sin(Math.PI*2*config.waveOffset*-1 + Math.PI*2*(1-config.waveCount) + d.y*2*Math.PI));} )
           .y1(function(d) { return (fillCircleRadius*2 + waveHeight); } );
       var waveGroup = gaugeGroup.append("defs")
           .append("clipPath")
           .attr("id", "clipWave" + elementId);
       var wave = waveGroup.append("path")
           .datum(data)
           .attr("d", clipArea)
           .attr("T", 0);

       // The inner circle with the clipping wave attached.
       var fillCircleGroup = gaugeGroup.append("g")
           .attr("clip-path", "url(#clipWave" + elementId + ")");
       fillCircleGroup.append("circle")
           .attr("cx", radius)
           .attr("cy", radius)
           .attr("r", fillCircleRadius)
           .style("fill", config.waveColor);

       // Text where the wave does overlap.
       var text2 = fillCircleGroup.append("text")
           .text(textRounder(textStartValue) + percentText)
           .attr("class", "liquidFillGaugeText")
           .attr("text-anchor", "middle")
           .attr("font-size", textPixels + "px")
           .style("fill", config.waveTextColor)
           .attr('transform','translate('+radius+','+textRiseScaleY(config.textVertPosition)+')');

       // Make the value count up.
       if(config.valueCountUp){
           var textTween = function(){
               var i = d3.interpolate(this.textContent, textFinalValue);
               return function(t) { this.textContent = textRounder(i(t)) + percentText; }
           };
           text1.transition()
               .duration(config.waveRiseTime)
               .tween("text", textTween);
           text2.transition()
               .duration(config.waveRiseTime)
               .tween("text", textTween);
       }

       // Make the wave rise. wave and waveGroup are separate so that horizontal and vertical movement can be controlled independently.
       var waveGroupXPosition = fillCircleMargin+fillCircleRadius*2-waveClipWidth;
       if(config.waveRise){
           waveGroup.attr('transform','translate('+waveGroupXPosition+','+waveRiseScale(0)+')')
               .transition()
               .duration(config.waveRiseTime)
               .attr('transform','translate('+waveGroupXPosition+','+waveRiseScale(fillPercent)+')')
               .each("start", function(){ wave.attr('transform','translate(1,0)'); }); // This transform is necessary to get the clip wave positioned correctly when waveRise=true and waveAnimate=false. The wave will not position correctly without this, but it's not clear why this is actually necessary.
       } else {
           waveGroup.attr('transform','translate('+waveGroupXPosition+','+waveRiseScale(fillPercent)+')');
       }

       if(config.waveAnimate) animateWave();

       animateWave() {
           wave.attr('transform','translate('+waveAnimateScale(wave.attr('T'))+',0)');
           wave.transition()
               .duration(config.waveAnimateTime * (1-wave.attr('T')))
               .ease('linear')
               .attr('transform','translate('+waveAnimateScale(1)+',0)')
               .attr('T', 1)
               .each('end', function(){
                   wave.attr('T', 0);
                   animateWave(config.waveAnimateTime);
               });
       }
   }

and it complained about ';' expected at this line animateWave() {. Even if I totally removed animateWave() function, it started to complain about type not matched for textRounder function.

它抱怨';'期望在这一行animateWave(){。即使我完全删除了animateWave()函数,它也开始抱怨textRounder函数的类型不匹配。

I guess there must be some other things wrong trigger this no sense error. but I can't find it. Could you point it out?

我猜必须有其他一些错误触发这个没有意义的错误。但我找不到它。你能指出来吗?

I know the code is too long. If it's not suitable for a specific question, could you guys suggest what should I do when I want to reuse a javascript function in typescript?

我知道代码太长了。如果它不适合某个特定的问题,那么当我想在打字稿中重用一个javascript函数时,你们可以建议我该怎么办?

1 个解决方案

#1


0  

I created like this chart-liquid-fill-gauge.ts

我创建了这样的图表 - liquid-fill-gauge.ts

import { Component, Inject, ElementRef, ViewChild, Input } from '@angular/core';
import * as d3 from 'd3';

@Component({
    selector: 'view-chart-liquid-fill-gauge',
    template: '<div class="d3-chart" #chart></div>',
    inputs: ['options', 'data']
})
export class LiquidFillGaugeChartView {
    @ViewChild('chart') private chartContainer: ElementRef;
    @Input() private data: string;
    private margin: any = { top: 20, bottom: 20, left: 20, right: 20 };
    private chart: any;
    private width: number;
    private height: number;
    /**
     * Initializes the component with the root element.
     */
    constructor( @Inject(ElementRef) elementRef: ElementRef) {
        //this.el = elementRef.nativeElement;



    }

    ngOnInit() {
        this.createChart();
        if (this.data) {
           // this.updateChart();
        }
    }

    ngOnChanges() {
        if (this.chart) {
            this.updateChart();
        }
    }

    createChart() {
        let config: any;
        let element = this.chartContainer.nativeElement;
        this.width = element.offsetWidth - this.margin.left - this.margin.right;
        this.height = element.offsetHeight - this.margin.top - this.margin.bottom;
        let value = parseFloat(this.data);
        let gauge = d3.select(element).append('svg')
            .attr('width', element.offsetWidth)
            .attr('height', element.offsetHeight);

    if(config == null) config = this.liquidFillGaugeDefaultSettings();

    var radius = Math.min(parseInt(gauge.style("width")), parseInt(gauge.style("height")))/2;
    var locationX = parseInt(gauge.style("width"))/2 - radius;
    var locationY = parseInt(gauge.style("height"))/2 - radius;
    var fillPercent = Math.max(config.minValue, Math.min(config.maxValue, value))/config.maxValue;
    var elementId = "liquidFillGauge"+new Date().getUTCMilliseconds();
    var waveHeightScale;
    if(config.waveHeightScaling){
        waveHeightScale = d3.scaleLinear()
            .range([0,config.waveHeight,0])
            .domain([0,50,100]);
    } else {
        waveHeightScale = d3.scaleLinear()
            .range([config.waveHeight,config.waveHeight])
            .domain([0,100]);
    }

    var textPixels = (config.textSize*radius/2);
    var textFinalValue = parseFloat(value+"").toFixed(2);
    var textStartValue = config.valueCountUp?config.minValue:textFinalValue;
    var percentText = config.displayPercent?"%":"";
    var circleThickness = config.circleThickness * radius;
    var circleFillGap = config.circleFillGap * radius;
    var fillCircleMargin = circleThickness + circleFillGap;
    var fillCircleRadius = radius - fillCircleMargin;
    var waveHeight = fillCircleRadius*waveHeightScale(fillPercent*100);

    var waveLength = fillCircleRadius*2/config.waveCount;
    var waveClipCount = 1+config.waveCount;
    var waveClipWidth = waveLength*waveClipCount;


    // Data for building the clip wave area.
    var data = [];
    for(var i = 0; i <= 40*waveClipCount; i++){
        data.push({x: i/(40*waveClipCount), y: (i/(40))});
    }

    // Scales for drawing the outer circle.
    var gaugeCircleX = d3.scaleLinear().range([0,2*Math.PI]).domain([0,1]);
    var gaugeCircleY = d3.scaleLinear().range([0,radius]).domain([0,radius]);

    // Scales for controlling the size of the clipping path.
    var waveScaleX = d3.scaleLinear().range([0,waveClipWidth]).domain([0,1]);
    var waveScaleY = d3.scaleLinear().range([0,waveHeight]).domain([0,1]);

    // Scales for controlling the position of the clipping path.
    var waveRiseScale = d3.scaleLinear()
        // The clipping area size is the height of the fill circle + the wave height, so we position the clip wave
        // such that the it will overlap the fill circle at all when at 0%, and will totally cover the fill
        // circle at 100%.
        .range([(fillCircleMargin+fillCircleRadius*2+waveHeight),(fillCircleMargin-waveHeight)])
        .domain([0,1]);
    var waveAnimateScale = d3.scaleLinear()
        .range([0, waveClipWidth-fillCircleRadius*2]) // Push the clip area one full wave then snap back.
        .domain([0,1]);

    // Scale for controlling the position of the text within the gauge.
    var textRiseScaleY = d3.scaleLinear()
        .range([fillCircleMargin+fillCircleRadius*2,(fillCircleMargin+textPixels*0.7)])
        .domain([0,1]);

    // Center the gauge within the parent SVG.
    var gaugeGroup = gauge.append("g")
        .attr('transform','translate('+locationX+','+locationY+')');

    // Draw the outer circle.
    var gaugeCircleArc = d3.arc()
        .startAngle(gaugeCircleX(0))
        .endAngle(gaugeCircleX(1))
        .outerRadius(gaugeCircleY(radius))
        .innerRadius(gaugeCircleY(radius-circleThickness));
    gaugeGroup.append("path")
        .attr("d", gaugeCircleArc)
        .style("fill", config.circleColor)
        .attr('transform','translate('+radius+','+radius+')');

    // Rounding functions so that the correct number of decimal places is always displayed as the value counts up.
    var textRounder:any = function(value){ return Math.round(value); };
    if(parseFloat(textFinalValue) != parseFloat(textRounder(textFinalValue))){
       textRounder = function(value){ return parseFloat(value).toFixed(1); };
    }
    if(parseFloat(textFinalValue) != parseFloat(textRounder(textFinalValue))){
        textRounder = function(value){ return parseFloat(value).toFixed(2); };
    }


    // Text where the wave does not overlap.
    var text1 = gaugeGroup.append("text")
        .text(textRounder(textStartValue) + percentText)
        .attr("class", "liquidFillGaugeText")
        .attr("text-anchor", "middle")
        .attr("font-size", textPixels + "px")
        .style("fill", config.textColor)
        .attr('transform','translate('+radius+','+textRiseScaleY(config.textVertPosition)+')');

    // The clipping wave area.
    var clipArea = d3.area()
        .x(function(d) { return waveScaleX(d['x']); } )
        .y0(function(d) { return waveScaleY(Math.sin(Math.PI*2*config.waveOffset*-1 + Math.PI*2*(1-config.waveCount) + d['y']*2*Math.PI));} )
        .y1(function(d) { return (fillCircleRadius*2 + waveHeight); } );
    var waveGroup = gaugeGroup.append("defs")
        .append("clipPath")
        .attr("id", "clipWave"+elementId);
    var wave = waveGroup.append("path")
        .datum(data)
        .attr("d", clipArea)
        .attr("T", 0);

    // The inner circle with the clipping wave attached.
    var fillCircleGroup = gaugeGroup.append("g")
        .attr("clip-path", "url(#clipWave"+elementId+")");
    fillCircleGroup.append("circle")
        .attr("cx", radius)
        .attr("cy", radius)
        .attr("r", fillCircleRadius)
        .style("fill", config.waveColor);

    // Text where the wave does overlap.
    var text2 = fillCircleGroup.append("text")
        .text(textRounder(textStartValue) + percentText)
        .attr("class", "liquidFillGaugeText")
        .attr("text-anchor", "middle")
        .attr("font-size", textPixels + "px")
        .style("fill", config.waveTextColor)
        .attr('transform','translate('+radius+','+textRiseScaleY(config.textVertPosition)+')');

    // Make the value count up.
    if(config.valueCountUp){
        var textTween = function(){
            var i = d3.interpolate(text1.text, textFinalValue);
            return function(t) {text1.text(textRounder(i(t)) + percentText); }
        };
        var textTween2 = function(){
            var i = d3.interpolate(text2.text, textFinalValue);
            return function(t) {text2.text(textRounder(i(t)) + percentText); }
        };
        text1.transition()
            .duration(config.waveRiseTime)
            .tween("text", textTween);
        text2.transition()
            .duration(config.waveRiseTime)
            .tween("text", textTween2);
    }

    // Make the wave rise. wave and waveGroup are separate so that horizontal and vertical movement can be controlled independently.
    var waveGroupXPosition = fillCircleMargin+fillCircleRadius*2-waveClipWidth;
    if(config.waveRise){
        waveGroup.attr('transform','translate('+waveGroupXPosition+','+waveRiseScale(0)+')')
            .transition()
            .duration(config.waveRiseTime)
            .attr('transform','translate('+waveGroupXPosition+','+waveRiseScale(fillPercent)+')')
            .on("start", function(){ wave.attr('transform','translate(1,0)'); }); // This transform is necessary to get the clip wave positioned correctly when waveRise=true and waveAnimate=false. The wave will not position correctly without this, but it's not clear why this is actually necessary.
    } else {
        waveGroup.attr('transform','translate('+waveGroupXPosition+','+waveRiseScale(fillPercent)+')');
    }

    if(config.waveAnimate) animateWave(wave, config.waveAnimateTime);

    function animateWave(wave, animateTime) {
        wave.attr('transform','translate('+waveAnimateScale(wave.attr('T'))+',0)');
        wave.transition()
            .duration(animateTime * (1-wave.attr('T')))
            .ease(d3.easeLinear)
            .attr('transform','translate('+waveAnimateScale(1)+',0)')
            .attr('T', 1)
            .on('end', function(){
                wave.attr('T', 0);
                animateWave(wave, animateTime);
            });
    }

    }

    updateChart() {

    }

    liquidFillGaugeDefaultSettings() {
        return {
            minValue: 0, // The gauge minimum value.
            maxValue: 100, // The gauge maximum value.
            circleThickness: 0.05, // The outer circle thickness as a percentage of it's radius.
            circleFillGap: 0.05, // The size of the gap between the outer circle and wave circle as a percentage of the outer circles radius.
            circleColor: "#178BCA", // The color of the outer circle.
            waveHeight: 0.05, // The wave height as a percentage of the radius of the wave circle.
            waveCount: 1, // The number of full waves per width of the wave circle.
            waveRiseTime: 1000, // The amount of time in milliseconds for the wave to rise from 0 to it's final height.
            waveAnimateTime: 3000, // The amount of time in milliseconds for a full wave to enter the wave circle.
            waveRise: true, // Control if the wave should rise from 0 to it's full height, or start at it's full height.
            waveHeightScaling: true, // Controls wave size scaling at low and high fill percentages. When true, wave height reaches it's maximum at 50% fill, and minimum at 0% and 100% fill. This helps to prevent the wave from making the wave circle from appear totally full or empty when near it's minimum or maximum fill.
            waveAnimate: true, // Controls if the wave scrolls or is static.
            waveColor: "#178BCA", // The color of the fill wave.
            waveOffset: 0, // The amount to initially offset the wave. 0 = no offset. 1 = offset of one full wave.
            textVertPosition: .5, // The height at which to display the percentage text withing the wave circle. 0 = bottom, 1 = top.
            textSize: 1, // The relative height of the text to display in the wave circle. 1 = 50%
            valueCountUp: true, // If true, the displayed value counts up from 0 to it's final value upon loading. If false, the final value is displayed.
            displayPercent: true, // If true, a % symbol is displayed after the value.
            textColor: "#045681", // The color of the value text when the wave does not overlap it.
            waveTextColor: "#A4DBf8" // The color of the value text when the wave overlaps it.
        };
    }
}

And then chart-liquid-fill-gauge.css

然后是chart-liquid-fill-gauge.css

.d3-chart {
  width: 30%;
  height: 400px;
}

Add LiquidFillGaugeChartView in to app.module.ts

将LiquidFillGaugeChartView添加到app.module.ts中

import { LiquidFillGaugeChartView } from 'views/chart-liquid-fill-gauge';

@NgModule({
  declarations: [
    :
    LiquidFillGaugeChartView
  ],
  :
});

in your home.html or any other component

在您的home.html或任何其他组件中

<view-chart-liquid-fill-gauge [data]="55.5"></view-chart-liquid-fill-gauge>

Note: using D3 V4 and tested working for me.

注意:使用D3 V4并经过测试为我工作。

#1


0  

I created like this chart-liquid-fill-gauge.ts

我创建了这样的图表 - liquid-fill-gauge.ts

import { Component, Inject, ElementRef, ViewChild, Input } from '@angular/core';
import * as d3 from 'd3';

@Component({
    selector: 'view-chart-liquid-fill-gauge',
    template: '<div class="d3-chart" #chart></div>',
    inputs: ['options', 'data']
})
export class LiquidFillGaugeChartView {
    @ViewChild('chart') private chartContainer: ElementRef;
    @Input() private data: string;
    private margin: any = { top: 20, bottom: 20, left: 20, right: 20 };
    private chart: any;
    private width: number;
    private height: number;
    /**
     * Initializes the component with the root element.
     */
    constructor( @Inject(ElementRef) elementRef: ElementRef) {
        //this.el = elementRef.nativeElement;



    }

    ngOnInit() {
        this.createChart();
        if (this.data) {
           // this.updateChart();
        }
    }

    ngOnChanges() {
        if (this.chart) {
            this.updateChart();
        }
    }

    createChart() {
        let config: any;
        let element = this.chartContainer.nativeElement;
        this.width = element.offsetWidth - this.margin.left - this.margin.right;
        this.height = element.offsetHeight - this.margin.top - this.margin.bottom;
        let value = parseFloat(this.data);
        let gauge = d3.select(element).append('svg')
            .attr('width', element.offsetWidth)
            .attr('height', element.offsetHeight);

    if(config == null) config = this.liquidFillGaugeDefaultSettings();

    var radius = Math.min(parseInt(gauge.style("width")), parseInt(gauge.style("height")))/2;
    var locationX = parseInt(gauge.style("width"))/2 - radius;
    var locationY = parseInt(gauge.style("height"))/2 - radius;
    var fillPercent = Math.max(config.minValue, Math.min(config.maxValue, value))/config.maxValue;
    var elementId = "liquidFillGauge"+new Date().getUTCMilliseconds();
    var waveHeightScale;
    if(config.waveHeightScaling){
        waveHeightScale = d3.scaleLinear()
            .range([0,config.waveHeight,0])
            .domain([0,50,100]);
    } else {
        waveHeightScale = d3.scaleLinear()
            .range([config.waveHeight,config.waveHeight])
            .domain([0,100]);
    }

    var textPixels = (config.textSize*radius/2);
    var textFinalValue = parseFloat(value+"").toFixed(2);
    var textStartValue = config.valueCountUp?config.minValue:textFinalValue;
    var percentText = config.displayPercent?"%":"";
    var circleThickness = config.circleThickness * radius;
    var circleFillGap = config.circleFillGap * radius;
    var fillCircleMargin = circleThickness + circleFillGap;
    var fillCircleRadius = radius - fillCircleMargin;
    var waveHeight = fillCircleRadius*waveHeightScale(fillPercent*100);

    var waveLength = fillCircleRadius*2/config.waveCount;
    var waveClipCount = 1+config.waveCount;
    var waveClipWidth = waveLength*waveClipCount;


    // Data for building the clip wave area.
    var data = [];
    for(var i = 0; i <= 40*waveClipCount; i++){
        data.push({x: i/(40*waveClipCount), y: (i/(40))});
    }

    // Scales for drawing the outer circle.
    var gaugeCircleX = d3.scaleLinear().range([0,2*Math.PI]).domain([0,1]);
    var gaugeCircleY = d3.scaleLinear().range([0,radius]).domain([0,radius]);

    // Scales for controlling the size of the clipping path.
    var waveScaleX = d3.scaleLinear().range([0,waveClipWidth]).domain([0,1]);
    var waveScaleY = d3.scaleLinear().range([0,waveHeight]).domain([0,1]);

    // Scales for controlling the position of the clipping path.
    var waveRiseScale = d3.scaleLinear()
        // The clipping area size is the height of the fill circle + the wave height, so we position the clip wave
        // such that the it will overlap the fill circle at all when at 0%, and will totally cover the fill
        // circle at 100%.
        .range([(fillCircleMargin+fillCircleRadius*2+waveHeight),(fillCircleMargin-waveHeight)])
        .domain([0,1]);
    var waveAnimateScale = d3.scaleLinear()
        .range([0, waveClipWidth-fillCircleRadius*2]) // Push the clip area one full wave then snap back.
        .domain([0,1]);

    // Scale for controlling the position of the text within the gauge.
    var textRiseScaleY = d3.scaleLinear()
        .range([fillCircleMargin+fillCircleRadius*2,(fillCircleMargin+textPixels*0.7)])
        .domain([0,1]);

    // Center the gauge within the parent SVG.
    var gaugeGroup = gauge.append("g")
        .attr('transform','translate('+locationX+','+locationY+')');

    // Draw the outer circle.
    var gaugeCircleArc = d3.arc()
        .startAngle(gaugeCircleX(0))
        .endAngle(gaugeCircleX(1))
        .outerRadius(gaugeCircleY(radius))
        .innerRadius(gaugeCircleY(radius-circleThickness));
    gaugeGroup.append("path")
        .attr("d", gaugeCircleArc)
        .style("fill", config.circleColor)
        .attr('transform','translate('+radius+','+radius+')');

    // Rounding functions so that the correct number of decimal places is always displayed as the value counts up.
    var textRounder:any = function(value){ return Math.round(value); };
    if(parseFloat(textFinalValue) != parseFloat(textRounder(textFinalValue))){
       textRounder = function(value){ return parseFloat(value).toFixed(1); };
    }
    if(parseFloat(textFinalValue) != parseFloat(textRounder(textFinalValue))){
        textRounder = function(value){ return parseFloat(value).toFixed(2); };
    }


    // Text where the wave does not overlap.
    var text1 = gaugeGroup.append("text")
        .text(textRounder(textStartValue) + percentText)
        .attr("class", "liquidFillGaugeText")
        .attr("text-anchor", "middle")
        .attr("font-size", textPixels + "px")
        .style("fill", config.textColor)
        .attr('transform','translate('+radius+','+textRiseScaleY(config.textVertPosition)+')');

    // The clipping wave area.
    var clipArea = d3.area()
        .x(function(d) { return waveScaleX(d['x']); } )
        .y0(function(d) { return waveScaleY(Math.sin(Math.PI*2*config.waveOffset*-1 + Math.PI*2*(1-config.waveCount) + d['y']*2*Math.PI));} )
        .y1(function(d) { return (fillCircleRadius*2 + waveHeight); } );
    var waveGroup = gaugeGroup.append("defs")
        .append("clipPath")
        .attr("id", "clipWave"+elementId);
    var wave = waveGroup.append("path")
        .datum(data)
        .attr("d", clipArea)
        .attr("T", 0);

    // The inner circle with the clipping wave attached.
    var fillCircleGroup = gaugeGroup.append("g")
        .attr("clip-path", "url(#clipWave"+elementId+")");
    fillCircleGroup.append("circle")
        .attr("cx", radius)
        .attr("cy", radius)
        .attr("r", fillCircleRadius)
        .style("fill", config.waveColor);

    // Text where the wave does overlap.
    var text2 = fillCircleGroup.append("text")
        .text(textRounder(textStartValue) + percentText)
        .attr("class", "liquidFillGaugeText")
        .attr("text-anchor", "middle")
        .attr("font-size", textPixels + "px")
        .style("fill", config.waveTextColor)
        .attr('transform','translate('+radius+','+textRiseScaleY(config.textVertPosition)+')');

    // Make the value count up.
    if(config.valueCountUp){
        var textTween = function(){
            var i = d3.interpolate(text1.text, textFinalValue);
            return function(t) {text1.text(textRounder(i(t)) + percentText); }
        };
        var textTween2 = function(){
            var i = d3.interpolate(text2.text, textFinalValue);
            return function(t) {text2.text(textRounder(i(t)) + percentText); }
        };
        text1.transition()
            .duration(config.waveRiseTime)
            .tween("text", textTween);
        text2.transition()
            .duration(config.waveRiseTime)
            .tween("text", textTween2);
    }

    // Make the wave rise. wave and waveGroup are separate so that horizontal and vertical movement can be controlled independently.
    var waveGroupXPosition = fillCircleMargin+fillCircleRadius*2-waveClipWidth;
    if(config.waveRise){
        waveGroup.attr('transform','translate('+waveGroupXPosition+','+waveRiseScale(0)+')')
            .transition()
            .duration(config.waveRiseTime)
            .attr('transform','translate('+waveGroupXPosition+','+waveRiseScale(fillPercent)+')')
            .on("start", function(){ wave.attr('transform','translate(1,0)'); }); // This transform is necessary to get the clip wave positioned correctly when waveRise=true and waveAnimate=false. The wave will not position correctly without this, but it's not clear why this is actually necessary.
    } else {
        waveGroup.attr('transform','translate('+waveGroupXPosition+','+waveRiseScale(fillPercent)+')');
    }

    if(config.waveAnimate) animateWave(wave, config.waveAnimateTime);

    function animateWave(wave, animateTime) {
        wave.attr('transform','translate('+waveAnimateScale(wave.attr('T'))+',0)');
        wave.transition()
            .duration(animateTime * (1-wave.attr('T')))
            .ease(d3.easeLinear)
            .attr('transform','translate('+waveAnimateScale(1)+',0)')
            .attr('T', 1)
            .on('end', function(){
                wave.attr('T', 0);
                animateWave(wave, animateTime);
            });
    }

    }

    updateChart() {

    }

    liquidFillGaugeDefaultSettings() {
        return {
            minValue: 0, // The gauge minimum value.
            maxValue: 100, // The gauge maximum value.
            circleThickness: 0.05, // The outer circle thickness as a percentage of it's radius.
            circleFillGap: 0.05, // The size of the gap between the outer circle and wave circle as a percentage of the outer circles radius.
            circleColor: "#178BCA", // The color of the outer circle.
            waveHeight: 0.05, // The wave height as a percentage of the radius of the wave circle.
            waveCount: 1, // The number of full waves per width of the wave circle.
            waveRiseTime: 1000, // The amount of time in milliseconds for the wave to rise from 0 to it's final height.
            waveAnimateTime: 3000, // The amount of time in milliseconds for a full wave to enter the wave circle.
            waveRise: true, // Control if the wave should rise from 0 to it's full height, or start at it's full height.
            waveHeightScaling: true, // Controls wave size scaling at low and high fill percentages. When true, wave height reaches it's maximum at 50% fill, and minimum at 0% and 100% fill. This helps to prevent the wave from making the wave circle from appear totally full or empty when near it's minimum or maximum fill.
            waveAnimate: true, // Controls if the wave scrolls or is static.
            waveColor: "#178BCA", // The color of the fill wave.
            waveOffset: 0, // The amount to initially offset the wave. 0 = no offset. 1 = offset of one full wave.
            textVertPosition: .5, // The height at which to display the percentage text withing the wave circle. 0 = bottom, 1 = top.
            textSize: 1, // The relative height of the text to display in the wave circle. 1 = 50%
            valueCountUp: true, // If true, the displayed value counts up from 0 to it's final value upon loading. If false, the final value is displayed.
            displayPercent: true, // If true, a % symbol is displayed after the value.
            textColor: "#045681", // The color of the value text when the wave does not overlap it.
            waveTextColor: "#A4DBf8" // The color of the value text when the wave overlaps it.
        };
    }
}

And then chart-liquid-fill-gauge.css

然后是chart-liquid-fill-gauge.css

.d3-chart {
  width: 30%;
  height: 400px;
}

Add LiquidFillGaugeChartView in to app.module.ts

将LiquidFillGaugeChartView添加到app.module.ts中

import { LiquidFillGaugeChartView } from 'views/chart-liquid-fill-gauge';

@NgModule({
  declarations: [
    :
    LiquidFillGaugeChartView
  ],
  :
});

in your home.html or any other component

在您的home.html或任何其他组件中

<view-chart-liquid-fill-gauge [data]="55.5"></view-chart-liquid-fill-gauge>

Note: using D3 V4 and tested working for me.

注意:使用D3 V4并经过测试为我工作。