我正在构建一个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看起来像这样