How would I convert the current code to something like this pressure chart - where rectangles are arranged in an arc -- and then the best case tick is highlighted to represent the value.
So we would append rectangles and change the angle as we create the skeleton.
I've got this demo that creates a spread of rectangles and changes the angles - but I am not sure how to stabilize this and create the arc from the left side - https://jsfiddle.net/smo6kqzw/3/
var svg = d3.select('body').append('svg').attr("height", 500).attr("width", 500).attr("class", "svger")
var rectbuf = 50;
var padd = 1;
for (let i = 0; i < 20; i++) {
svg.append("rect")
.attr("width", 30)
.attr("height", 20)
.attr("fill", "red")
.attr("y", 20)
.attr("x", rectbuf * 5)
.attr("transform", "rotate("+((5+padd) *i)+")")
}
// create svg element:
var svg = d3.select("#rect").append("svg").attr("width", 800).attr("height", 200)
// Add the path using this helper function
svg.append('rect')
.attr('x', 10)
.attr('y', 120)
.attr('width', 600)
.attr('height', 40)
.attr('stroke', 'black')
.attr('fill', '#69a3b2');
I currently have a dial chart that looks like this
import React from 'react'
import * as d3 from 'd3'
class GaugeChart extends React.Component {
constructor(props) {
super(props)
this.myRef = React.createRef()
this.state = {
data: [],
theme: this.props.theme
? this.props.theme
: ['#bde0fe', '#2698f9', '#71bcfd', '#f1f8fe'],
}
}
componentDidMount() {
var $this = this.myRef.current
d3.select($this).selectAll('svg').remove()
const value = this.props.value
const data = [
{
label: 'Completed',
value: value,
},
{
label: 'Remaining',
value: 100 - value,
},
]
const width = parseInt(this.props.width, 10),
height = parseInt(this.props.height, 10),
radius = parseInt(this.props.r, 10),
innerradius = parseInt(this.props.ir, 10)
var color = d3.scaleOrdinal().range(this.state.theme)
var arc = d3.arc().outerRadius(radius).innerRadius(innerradius)
data.forEach(function (d) {
d.total = +d.value
})
var pie = d3
.pie()
.startAngle(-90 * (Math.PI / 180))
.endAngle(90 * (Math.PI / 180))
.padAngle(0.02) // some space between slices
.sort(null)
.value(function (d) {
return d.total
})
var svg = d3
.select($this)
.append('svg')
.attr('width', width)
.attr('height', height + 5)
.append('g')
.attr('class', 'piechart')
.attr('transform', 'translate(' + width / 2 + ',' + height + ')')
var segments = svg.append('g').attr('class', 'segments')
var slices = segments
.selectAll('.arc')
.data(pie(data))
.enter()
.append('g')
.attr('class', 'arc')
slices
.append('path')
.attr('d', arc)
.attr('fill', function (d, i) {
return color(i)
})
/*.transition()
.attrTween('d', function(d) {
var i = d3.interpolate(d.startAngle + 0.1, d.endAngle);
return function(t) {
d.endAngle = i(t);
return arc(d);
}
} )*/
var arrow = svg.append('g').attr('transform', 'rotate( 0 )')
arrow
.append('path')
.attr('fill', '#ef7e60')
.attr(
'd',
`M -${innerradius - 5},0 -${innerradius - 12},-5 -${
innerradius - 12
},5`,
) // draw triangle
var text = svg
.append('text')
.attr('text-anchor', 'middle')
.attr('dy', 0)
.attr('fill', '#ef7e60')
function update(data) {
text.text(`${Math.round(data[0].value)}%`) // set % text
arrow.transition().attr('transform', `rotate( ${data[0].value * 1.8} )`) //rotate to desired qantity ( i put to 60% of 180 deg )
}
update(data)
}
render() {
return <div ref={this.myRef} className="GaugeChart" />
}
}
export default GaugeChart
-- latest fiddle 2nd Dec https://jsfiddle.net/gp4wL80t/
latest fiddle 3rd Dec https://jsfiddle.net/0k7zvdxn/
Regarding your demo code re...
"I've got this demo that creates a spread of rectangles and changes the angles - but I am not sure how to stabilize this and create the arc from the left side"
Really you just need to modify the starting angle then calculate the positions for the ticks directly along the arc.
EDIT
You can make the bars thicker by using stroke-width
, go around the edges more by adjusting the startAngle
and endAngle
(in radians).
To highlight the nearest marker that would approximate the value, and also display the value in the middle/bottom like the pressure example - you just need to calculate the marker based on the value and display the text, etc - e.g.
const value = Math.floor(Math.random() * 100); // value you want to show, random here just as an example...
const numTicks = 50; // total ticks around the edge
const lineThickness = 3; // width of ticks
const tickLength = 10; // length of ticks
const arcRadius = 55; // size of the arc
const startAngle = -220 * (Math.PI / 180); // first tick pos in rad
const endAngle = 40 * (Math.PI / 180); // last tick pos in rad
const centerX = 75;
const centerY = 75;
const width = 150;
const height = 150;
const svg = d3.select('body')
.append('svg')
.attr("height", height)
.attr("width", width)
.attr("class", "svger");
for (let i = 0; i < numTicks; i++) {
const angle = startAngle + (i / (numTicks - 1)) * (endAngle - startAngle);
const isHighlighted = i === Math.round((value / 100) * (numTicks - 1));
const highlightAmmount = isHighlighted ? 2 : 0; // highlight sizing
const x1 = centerX + arcRadius * Math.cos(angle);
const y1 = centerY + arcRadius * Math.sin(angle);
const x2 = x1 + (tickLength + highlightAmmount) * Math.cos(angle);
const y2 = y1 + (tickLength + highlightAmmount) * Math.sin(angle);
const tick = svg.append("line")
.attr("x1", x1)
.attr("y1", y1)
.attr("x2", x2)
.attr("y2", y2)
.attr("stroke", isHighlighted ? "#ffffff" : '#6294d5') // highlight colour
.attr("stroke-linecap", "round")
.attr("stroke-width", lineThickness + highlightAmmount);
}
svg.append("text")
.attr("x", centerX)
.attr("y", centerY)
.text(value)
.attr("text-anchor", "middle")
.attr("alignment-baseline", "middle")
.attr("font-family", "Arial")
.attr("font-size", "30px")
.attr("font-weight", "bold")
.attr("fill", "#ffffff");
svg.append("text")
.attr("x", 35)
.attr("y", height - 15)
.text("Low")
.attr("text-anchor", "middle")
.attr("alignment-baseline", "middle")
.attr("font-family", "Arial")
.attr("font-size", "12px")
.attr("fill", "#ffffff");
svg.append("text")
.attr("x", width - 35)
.attr("y", height - 15)
.text("High")
.attr("text-anchor", "middle")
.attr("alignment-baseline", "middle")
.attr("font-family", "Arial")
.attr("font-size", "12px")
.attr("fill", "#ffffff");
.svger {
background: #4c7dbb;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
I also styled it to look more like your example. Note the value I've displayed is random just to show how the highlighting is working. i.e. each time you click run code snippet
a different value is shown :)
I also made a fiddle if you want to play with it - https://jsfiddle.net/fraser/9nyc5eb1/