d3.js液体图表bug从圆形到矩形

时间:2017-09-01 13:51:15

标签: javascript d3.js

我正在构建一个d3.js版本4 - 液体图表 - 它来自这个水图圆形仪表 - 但我希望制作方形条/变体。

错误/问题 - 修复代码,使液体图表正确填充为条形百分比。

- 这两个jsfiddles之间的区别只是config1.fillShape参数。 - rect / circle

//破碎的条形码

http://jsfiddle.net/0ht35rpb/132/

//使用旧圆规版

http://jsfiddle.net/0ht35rpb/133/

我不确定需要改变哪些才能使其正常运作

我一直专注于已加星标的方面 - 尝试使用config.height作为方程中的变量来测试结果 - 但它不会产生稳定的结果 - 1%和99%范围。

waveHeightScale 
waveHeightScaling
waveRiseScale 
clipArea*
fillGroup*
waveRise

我在尝试让waveHeight / clipArea理解额外的条形高度时遇到了问题 - 当我过去玩过它时 - 它填满了更多空间 - 但是条形高度值没有达到。感觉正确 - 就像1%太高了。

这是实际的代码 - 它将成为reactjs组件的一部分。

import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import * as d3 from "d3";
import $ from 'jquery';
import _ from 'underscore';
import './liquidchart.css';

class LiquidChart extends Component {
    componentDidMount() {
        var $this = $(ReactDOM.findDOMNode(this));

        console.log("chart--< this.props",this.props)

        var val = $this.data("val");

        var config = liquidFillGaugeDefaultSettings();
            config.backgroundColor = this.props.backgroundColor;
            config.textColor = this.props.textColor;
            config.waveTextColor = this.props.waveTextColor;
            config.waveStartColor = this.props.waveStartColor;
            config.waveColorDuration = this.props.waveColorDuration;
            config.waveColor = this.props.waveColor;
            config.fillShape = this.props.fillShape;
            config.circleThickness = this.props.circleThickness;
            config.textVertPosition = this.props.textVertPosition;
            config.waveAnimateTime = this.props.waveAnimateTime;
            config.displayText = this.props.displayText == "true";
            config.height = this.props.height;
            config.width = this.props.width;
            config.displayOverlay = this.props.displayOverlay == "true";
            config.overlayImageSrc = this.props.overlayImageSrc;
            config.overlayImageHeight = this.props.overlayImageHeight;
            config.overlayImageWidth = this.props.overlayImageWidth;
            config.axisLabel = this.props.axisLabel;

        var gauge = loadLiquidFillGauge($this, val, config);

        function liquidFillGaugeDefaultSettings(){
            return {
                height: 90,
                width: 90,
                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.
                backgroundColor: "#178BCA", // The color of the outer circle.
                waveHeight: 0.1, // The wave height as a percentage of the radius of the wave circle.
                waveCount: 2, // The number of full waves per width of the wave circle.
                waveRiseTime: 2000, // The amount of time in milliseconds for the wave to rise from 0 to it's final height.
                waveAnimateTime: 1500, // 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: .25, // 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.
                fillShape: "rect", // circle or rect - shape of wave
                waveStartColor: "gold", // starting color
                waveColorDuration : 1000, // how long it takes to change from starting color to wave color
                displayText: true, // display the percentage text
                displayOverlay: false, // display the overlay
                overlayImageSrc: "", // overlay image source
                overlayImageHeight: "", // overlay image height
                overlayImageWidth: "", // overlay image width
                axisLabel: "" //display a label at the bottom axis
            };
        }

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


            const chart = d3.select(elementId[0])
                            .append("svg")                      
                                .attr("width", config.width)
                                .attr("height", config.height);

            const gauge = chart
                            .append("g")
                                .attr('transform','translate(0,0)');

            if(config.displayOverlay){
                const imgs = chart
                                .append("g")
                                    .attr('transform','translate(0,0)')
                                        .append("svg:image")
                                        .attr("xlink:href", config.overlayImageSrc)
                                        .attr("x", "0")
                                        .attr("y", "0")
                                        .attr("width", config.overlayImageWidth)
                                        .attr("height", config.overlayImageHeight);
            }

            if(config.axisLabel){
                const axisLabel = chart
                                    .append("g")
                                        .append("text")
                                          .attr("x", config.width/2)
                                          .attr("y", config.height)
                                          .attr("dy", "-4px")
                                          .style("text-anchor", "middle")
                                          .text(config.axisLabel);

            }


            const randId = _.uniqueId('liquid_');
            const radius = Math.min(parseInt(config.width), parseInt(config.height))/2;

            const locationX = parseInt(config.width)/2 - radius;
            var locationY = parseInt(config.height)/2 - radius;

            if(config.fillShape == "rect"){
                locationY = 0;
            }

            const fillPercent = Math.max(config.minValue, Math.min(config.maxValue, value))/config.maxValue;

            let waveHeightScale = null;
            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]);
            }

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

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

            // Rounding functions so that the correct number of decimal places is always displayed as the value counts up.
            let 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.
            const data = [];
            for(let i = 0; i <= 40*waveClipCount; i++){
                data.push({x: i/(40*waveClipCount), y: (i/(40))});
            }

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

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

            // Scales for controlling the position of the clipping path.

            // 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%.

            const waveRiseScale = d3.scaleLinear()
                .range([(fillCircleMargin+fillCircleRadius*2+waveHeight),(fillCircleMargin-waveHeight)])
                .domain([0,1]);

            const 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.
            const textRiseScaleY = d3.scaleLinear()
                .range([fillCircleMargin+fillCircleRadius*2,(fillCircleMargin+textPixels*0.7)])
                .domain([0,1]);

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

            var drawOuterShell = function(){
                // Draw the outer circle.
                const 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.backgroundColor)
                    .attr('transform','translate('+radius+','+radius+')');
            }

            var drawOuterBlock = function(){
                // Draw the outer block.
                gaugeGroup.append("rect")
                    .attr("x", 0)
                    .attr("y", 0)
                    .attr("width", config.width)
                    .attr("height", config.height)
                    .style("fill", config.backgroundColor);
            }

            if(config.fillShape == "circle"){
                drawOuterShell();
            } else {
                drawOuterBlock();
            }

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

                // Make the value count up.
                if(config.valueCountUp){
                    text.transition()
                        .duration(config.waveRiseTime)
                        .tween("text", function() {
                          const i = d3.interpolateNumber(textInterpolatorValue, textFinalValue);
                          return (t) => {
                            textInterpolatorValue = textRounder(i(t));
                            // Set the gauge's text with the new value and append the % sign
                            // to the end
                            text.text(textInterpolatorValue + percentText);
                          }
                        });
                }
            }

            if(config.displayText){
                appendText(gaugeGroup, config.textColor);
            }

            // The clipping wave area.
            const 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));} );

            if(config.fillShape == "circle") {
                clipArea
                    .y1(function(d) { return (fillCircleRadius * 2 + waveHeight); } );
            } else {
                clipArea
                    .y1(function(d) { return (fillCircleRadius * 2 + waveHeight); } );
                    //.y1(function(d) { return (config.height - (fillCircleRadius * 2) + waveHeight); } );
            }


            const waveGroup = gaugeGroup.append("defs")
                .append("clipPath")
                .attr("id", "clipWave" + randId);
            const wave = waveGroup.append("path")
                .datum(data)
                .attr("d", clipArea)
                .attr("T", 0);

            // The inner circle with the clipping wave attached.
            const fillGroup = gaugeGroup.append("g")
                .attr("clip-path", "url(#clipWave" + randId + ")");

            var drawShapeWave = function(shape){
                // Draw the wave shape.

                if(shape == "circle") {
                    fillGroup.append("circle")
                        .attr("cx", radius)
                        .attr("cy", radius)
                        .attr("r", fillCircleRadius);
                }else {
                    fillGroup.append("rect")
                        .attr("x", radius - fillCircleRadius)
                        .attr("y", radius - fillCircleRadius)
                        .attr("width", fillCircleRadius * 2)
                        .attr("height", fillCircleRadius * 2)
                        //.attr("height", config.height - (fillCircleRadius * 2));
                }

                fillGroup
                    .style("fill", config.waveStartColor)
                    .transition()
                    .duration(config.waveColorDuration)
                    .style("fill", config.waveColor);
            }

            drawShapeWave(config.fillShape);

            if(config.displayText){             
                appendText(fillGroup, config.waveTextColor);
            }

            // Make the wave rise. wave and waveGroup are separate so that horizontal and vertical movement can be controlled independently.
            const 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();

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

    }
    render() {
        return (
            <div 
                className="thermometer" 
                data-role="thermometer"
                data-backgroundColor = {this.props.backgroundColor}
                data-textColor = {this.props.textColor}
                data-waveTextColor = {this.props.waveTextColor}
                data-waveStartColor = {this.props.waveStartColor}
                data-waveColorDuration = {this.props.waveColorDuration}
                data-waveColor = {this.props.waveColor}
                data-fillShape = {this.props.fillShape}
                data-circleThickness = {this.props.circleThickness}
                data-textVertPosition = {this.props.textVertPosition}
                data-waveAnimateTime = {this.props.waveAnimateTime}
                data-displayText = {this.props.displayText}
                data-height = {this.props.height}
                data-width = {this.props.width}
                data-displayOverlay = {this.props.displayOverlay}
                data-overlayImageSrc = {this.props.overlayImageSrc}
                data-overlayImageHeight = {this.props.overlayImageHeight}
                data-overlayImageWidth = {this.props.overlayImageWidth}
                data-val = {this.props.val}
                data-axisLabel = {this.props.axisLabel}
                >
            </div>
        );
    }
};

export default LiquidChart;

所以不要这样。

w- 70 h-200

enter image description here

看起来像这样

enter image description here

0 个答案:

没有答案