radialGauge = (props) => {
const el = d3.create('svg').nodes()[0]
for (const [key, val] of Object.entries(props.style || {})) {
el.style[key] = val
}
const init = (el, props) => {
const {direction, zones, neutralColor, transitionTime, relativeArrowLength,
relativeArrowTickness} = Object.assign({
direction: "down",
zones: [],
neutralColor: "black",
transitionTime: 0,
relativeArrowLength: 0.7,
relativeArrowTickness: 1
}, props)
const arrowPointTickness = 5;
const baseArrowTickness = 20;
const arrowTickness = baseArrowTickness * relativeArrowTickness
const cwidth = 300;
const cheight = (cwidth / 2) + (arrowTickness / 2);
const start = - (Math.PI * 0.5);
const end = Math.PI * 0.5;
const outerRadius = cwidth / 2;
const tickness = 15;
const innerRadius = outerRadius - tickness;
const baseArrowLength = innerRadius;
const arrowLength = baseArrowLength * relativeArrowLength
const scale = d3.scaleLinear()
.domain([0, 1])
.range([(start * (180 / Math.PI))+2, (end * (180 / Math.PI)) - 2])
.clamp(true);
const svg = d3.select(el)
.attr('viewBox', _.includes(['up', 'down'], direction)
? [-cwidth / 2, -cwidth / 2, cwidth, cheight]
: [-cwidth / 2, -cwidth / 2, cheight, cwidth]);
const g = svg.append('g')
switch (direction) {
case 'left': {
g.attr('transform', `translate(${(-cwidth / 2) + (arrowTickness / 2)}, 0) rotate(90) scale(-1, 1)`)
break
}
case 'up': {
g.attr('transform', `rotate(180, 0, ${(-cwidth / 4) + (arrowTickness / 4)}) scale(-1, 1)`)
break
}
case 'right': {
g.attr('transform', `rotate(-90)`)
break
}
}
const arc = d3.arc()
.outerRadius(outerRadius)
.innerRadius(innerRadius)
.startAngle(start)
.endAngle(end);
g.append("path")
.classed('background', true)
.attr("d", arc)
.style('fill-opacity', 0.5)
.style('transition', `all ${transitionTime}s linear`)
const clipPathId = _.uniqueId('circle-clip')
svg.append("clipPath")
.attr("id", clipPathId)
.append("path")
.attr('d', arc)
.classed('circle-clip-path', true)
.style('transition', `all ${transitionTime}s linear`)
g.append("path")
.classed('foreground', true)
.attr("d", arc)
.attr("clip-path",`url(#${clipPathId})`)
.style('transition', `all ${transitionTime}s linear`)
for (const zone of zones) {
const from = Math.min(zone[0], zone[1])
const to = Math.max(zone[0], zone[1])
const zoneGap = 5;
const zoneSize = 5;
const startAngle = (end - start) * from + start;
const endAngle = (end - start) * to + start;
const zoneArc = d3.arc()
.outerRadius(innerRadius - zoneGap)
.innerRadius(innerRadius - zoneGap - zoneSize)
.startAngle(startAngle)
.endAngle(endAngle);
g.append("path")
.attr("d", zoneArc)
.style("fill", neutralColor);
const handleSize = 15;
const handleWidth = 0.02;
const zoneStartArc = d3.arc()
.outerRadius(innerRadius - zoneGap)
.innerRadius(innerRadius - zoneGap - handleSize)
.startAngle(Math.max(startAngle - handleWidth / 2, start))
.endAngle(Math.max(startAngle - handleWidth / 2, start) + handleWidth);
g.append("path")
.attr("d", zoneStartArc)
.style("fill", neutralColor);
const zoneEndArc = d3.arc()
.outerRadius(innerRadius - zoneGap)
.innerRadius(innerRadius - zoneGap - handleSize)
.startAngle(Math.min(endAngle + handleWidth / 2, end) - handleWidth)
.endAngle(Math.min(endAngle + handleWidth / 2, end));
g.append("path")
.attr("d", zoneEndArc)
.style("fill", neutralColor);
}
const arrowPath = (() => {
const p = d3.path();
const upper = -innerRadius;
const lower = -innerRadius + arrowLength;
p.moveTo(0, lower);
p.lineTo((-arrowTickness / 2), lower);
p.lineTo(- (arrowPointTickness / 2), upper);
p.lineTo((arrowPointTickness / 2), upper);
p.lineTo((arrowTickness / 2), lower);
p.lineTo(0, lower);
return p;
})();
g.append("path")
.classed('arrow', true)
.attr("d", arrowPath)
.style('transition', `all ${transitionTime}s linear`)
el.update = update
el.initParams = {
scale
}
el.update(props)
};
const update = function(props) {
const {value, color} = Object.assign({
value: 0.5,
color: 'black'
}, props)
const {scale} = this.initParams
const svg = d3.select(this)
svg.select(".background")
.style("fill", color)
svg.select(".foreground")
.style("fill", color)
svg.select('.circle-clip-path')
.attr('transform', `rotate(${scale(value) - 90},0,0)`)
svg.select('.arrow')
.attr("transform", `rotate(${scale(value)},0,0)`)
.style("fill", color)
};
init(el, props)
return el
}