-
Notifications
You must be signed in to change notification settings - Fork 10
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add Stat Spark Lines & Energy Mix Pie Chart #116
Changes from 17 commits
530d507
57f552f
ba40da1
0158530
1fa5e8d
171765c
1fbd1a8
afdf020
afd2c6c
b277579
487a8ce
c259a3c
d251fc9
bb63dd4
739298d
8b41d67
b8ee9ca
0b983cd
d38ce0a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,171 @@ | ||
<template> | ||
<div class="pie-chart-cont"> | ||
<svg id="pie-chart"><!-- D3 inserts here --></svg> | ||
</div> | ||
</template> | ||
|
||
<script lang="ts"> | ||
import { Component, Prop, Vue, Watch } from 'vue-property-decorator'; | ||
import * as d3 from 'd3'; | ||
|
||
export interface IPieSlice { | ||
value: number; | ||
label: string; | ||
color: string; | ||
} | ||
|
||
/** | ||
* A component that can graph an arbitrary array of numeric data as a pie chart | ||
* | ||
* E.g. Votes for favorite pies: | ||
* [ { label: 'Cherry', value: 30 }, { label: 'Chocolate', value: 12 }] | ||
*/ | ||
@Component({}) | ||
export default class PieChart extends Vue { | ||
@Prop({required: true}) graphData!: Array<IPieSlice>; | ||
|
||
@Watch('graphData') | ||
onDataChanged(): void { | ||
this.renderGraph(); | ||
} | ||
|
||
readonly width = 400; | ||
readonly height = 320; | ||
|
||
readonly graphMargins = { top: 0, right: 0, bottom: 0, left: 0 }; | ||
|
||
svg!: d3.Selection<SVGGElement, unknown, HTMLElement, any>; | ||
|
||
mounted(): void { | ||
const outerWidth = this.width + this.graphMargins.left + this.graphMargins.right; | ||
const outerHeight = this.height + this.graphMargins.top + this.graphMargins.bottom; | ||
|
||
this.svg = d3 | ||
.select("svg#pie-chart") | ||
.attr("width", outerWidth) | ||
.attr("height", outerHeight) | ||
.attr("viewBox", `0 0 ${outerWidth} ${outerHeight}`) | ||
.attr("preserveAspectRatio", "xMidYMid meet") | ||
.append("g") | ||
.attr("transform", `translate(${this.width / 2},${this.height / 2})`); | ||
|
||
this.renderGraph(); | ||
} | ||
|
||
renderGraph(): void { | ||
// Empty the SVG | ||
this.svg.html(null); | ||
|
||
const radius = 100; | ||
const labelRadius = 150; | ||
|
||
// Compute the position of each group on the pie: | ||
var pie = d3.pie() | ||
.value((d: any) => d.value); | ||
Check warning Code scanning / ESLint Disallow the `any` type Warning
Unexpected any. Specify a different type.
|
||
var dataReady = pie(this.graphData as any); | ||
Check warning Code scanning / ESLint Disallow the `any` type Warning
Unexpected any. Specify a different type.
|
||
|
||
// shape helper to build arcs: | ||
var arcGenerator = d3.arc() | ||
.innerRadius(0) | ||
.outerRadius(radius); | ||
|
||
var labelArcGenerator = d3.arc() | ||
.innerRadius(radius) | ||
.outerRadius(labelRadius); | ||
|
||
// Build the pie chart: Basically, each part of the pie is a path that we build using the | ||
// arc function. | ||
this.svg.selectAll('mySlices') | ||
.data(dataReady) | ||
.enter() | ||
.append('path') | ||
.attr('d', arcGenerator as any) | ||
Check warning Code scanning / ESLint Disallow the `any` type Warning
Unexpected any. Specify a different type.
|
||
.attr('fill', (d) => (d.data as unknown as IPieSlice).color); | ||
|
||
|
||
// Calculate total value for % calculation | ||
let totalValue = 0; | ||
this.graphData.forEach((d) => totalValue += d.value); | ||
|
||
/** Add pie chart labels */ | ||
this.svg.selectAll('mySlices') | ||
.data(dataReady) | ||
.enter() | ||
.append('text') | ||
.html((d) => { | ||
// Convert degrees to rads | ||
const thresholdRadians = 5 / 360 * 2 * Math.PI; | ||
|
||
// If we have a lot of small slices, we skip those labels so they don't collide | ||
if (this.graphData.length > 2 | ||
&& (d.endAngle - d.startAngle) < thresholdRadians) { | ||
return ''; | ||
} | ||
|
||
let data = d.data as any as IPieSlice; | ||
Check warning Code scanning / ESLint Disallow the `any` type Warning
Unexpected any. Specify a different type.
|
||
|
||
const label = | ||
`<tspan class="percent">${this.calculatePercentage(data.value, totalValue)}%</tspan>` + | ||
`<tspan class="label" x="0" dy="1.5em">${data.label}</tspan>`; | ||
|
||
return label; | ||
}) | ||
.attr('class', () => this.graphData.length === 1 ? '-only-slice' : '') | ||
.attr("transform", (d) => { | ||
// If we have only 1 slice (e.g. 100% electric, like Marina Towers), place dead center, | ||
// otherwise use secondary arc centroid | ||
if (this.graphData.length === 1) { | ||
return ''; | ||
} | ||
|
||
return `translate(${labelArcGenerator.centroid(d as unknown as d3.DefaultArcObject)})`; | ||
}) | ||
.style("text-anchor", (d) => { | ||
// Center single slice label | ||
if (this.graphData.length === 1) { return 'middle'; } | ||
|
||
// are we past the center? | ||
return (d.endAngle + d.startAngle) / 2 > Math.PI ? | ||
"end" : "start"; | ||
}); | ||
} | ||
|
||
calculatePercentage(value: number, total: number): string { | ||
const percentage = value / total * 100; | ||
|
||
if (percentage < 1) { | ||
return '< 1'; | ||
} | ||
// If > 99%, we don't want to round to 100% so we can show there's other slices | ||
else if (percentage > 99 && percentage < 100) { | ||
return Math.floor(percentage).toString(); | ||
} | ||
else { | ||
return Math.round(percentage).toString(); | ||
} | ||
} | ||
} | ||
</script> | ||
|
||
<style lang="scss"> | ||
.pie-chart-cont { | ||
svg { | ||
width: 100%; | ||
height: auto; | ||
max-width: 50rem; | ||
|
||
path { | ||
stroke: $white; | ||
stroke-width: 0.25rem; | ||
} | ||
|
||
text.-only-slice { font-size: 1.5rem; } | ||
} | ||
|
||
tspan.percent { | ||
font-weight: bold; | ||
font-size: 1.3em; | ||
} | ||
tspan.label { font-size: 0.65em; } | ||
} | ||
</style> |
Check warning
Code scanning / ESLint
Disallow the `any` type Warning