aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Analyze.tsx6
-rw-r--r--src/Chart.tsx254
-rw-r--r--src/Doughnut.tsx (renamed from src/Donut.tsx)4
-rw-r--r--src/background.ts7
-rw-r--r--src/popup.tsx10
-rw-r--r--src/tab.tsx38
6 files changed, 228 insertions, 91 deletions
diff --git a/src/Analyze.tsx b/src/Analyze.tsx
index e7563d5..aa77cb3 100644
--- a/src/Analyze.tsx
+++ b/src/Analyze.tsx
@@ -21,11 +21,9 @@ import * as gapi from './gapi';
import { MsgType, MsgClient } from './msg';
import { Pattern, PatternEntry, PatternEntryFlat } from './pattern';
import { AnalyzePieChart } from './Chart';
-import { getGraphData } from './graph';
+import { getGraphData, PatternGraphData } from './graph';
-const defaultChartData = [
- {name: 'Work', value: 10, color: cyan[300]},
- {name: 'Wasted', value: 10, color: deepOrange[300]}];
+const defaultChartData = [] as PatternGraphData[];
const styles = (theme: Theme) => ({
buttonSpacer: {
diff --git a/src/Chart.tsx b/src/Chart.tsx
index 170b5fa..0c8f958 100644
--- a/src/Chart.tsx
+++ b/src/Chart.tsx
@@ -2,11 +2,24 @@ import React from 'react';
import { Theme, withStyles } from '@material-ui/core/styles';
import Grid from '@material-ui/core/Grid';
import cyan from '@material-ui/core/colors/cyan';
-import { Doughnut } from 'react-chartjs-2';
+import Typography from '@material-ui/core/Typography';
+import { Doughnut as DChart, Chart } from 'react-chartjs-2';
import 'chartjs-plugin-labels';
import Color from 'color';
-import { defaultChartColor } from './theme';
+import { defaultChartColor, theme } from './theme';
import { PatternGraphData } from './graph';
+import Doughnut from './Doughnut';
+
+declare module 'react-chartjs-2' {
+ export var Chart: {
+ controllers: {
+ doughnut: any,
+ pie: any
+ },
+ elements: { Arc: any },
+ pluginService: any
+ };
+}
const styles = (theme: Theme) => ({
pieChart: {
@@ -14,67 +27,176 @@ const styles = (theme: Theme) => ({
}
});
-type PatternPieChartProps = {
+interface PatternPieChartProps {
classes: {
patternTableWrapper: string,
pieChart: string
- },
- height?: number | string,
- data: PatternGraphData[],
- borderWidth: number,
- labelFontSize : number,
- paddingTop: number,
- paddingBottom: number,
- paddingLeft: number,
- paddingRight: number,
+ };
+ data: PatternGraphData[];
+ borderWidth: number;
+ labelFontSize : number;
+ paddingTop: number;
+ paddingBottom: number;
+ paddingLeft: number;
+ paddingRight: number;
};
+Chart.elements.Arc.prototype.draw = function() {
+ let ctx = this._chart.ctx;
+ const vm = this._view;
+ const sA = vm.startAngle;
+ const eA = vm.endAngle;
+ const pixelMargin = (vm.borderAlign === 'inner') ? 0.33 : 0;
+ let angleMargin;
+
+ ctx.save();
+
+ const delta = 3;
+ const deltaOuter = Math.asin(delta / vm.outerRadius);
+ const deltaInner = Math.asin(delta / vm.innerRadius);
+
+ let sA1 = sA;
+ let sA2 = sA;
+ let eA1 = eA;
+ let eA2 = eA;
+
+ if ((eA - sA) > 2 * deltaInner + 0.05) {
+ sA1 += deltaOuter;
+ eA1 -= deltaOuter;
+ sA2 += deltaInner;
+ eA2 -= deltaInner;
+ }
+
+ ctx.beginPath();
+ ctx.arc(vm.x, vm.y, Math.max(vm.outerRadius - pixelMargin, 0), sA1, eA1);
+ ctx.arc(vm.x, vm.y, vm.innerRadius, eA2, sA2, true);
+ ctx.closePath();
+
+ ctx.fillStyle = vm.backgroundColor;
+ ctx.fill();
+
+ if (vm.borderWidth) {
+ if (vm.borderAlign === 'inner') {
+ // Draw an inner border by cliping the arc and drawing a double-width border
+ // Enlarge the clipping arc by 0.33 pixels to eliminate glitches between borders
+ ctx.beginPath();
+ angleMargin = pixelMargin / vm.outerRadius;
+ ctx.arc(vm.x, vm.y, vm.outerRadius, sA - angleMargin, eA + angleMargin);
+ if (vm.innerRadius > pixelMargin) {
+ angleMargin = pixelMargin / vm.innerRadius;
+ ctx.arc(vm.x, vm.y, vm.innerRadius - pixelMargin, eA + angleMargin, sA - angleMargin, true);
+ } else {
+ ctx.arc(vm.x, vm.y, pixelMargin, eA + Math.PI / 2, sA - Math.PI / 2);
+ }
+ ctx.closePath();
+ ctx.clip();
+
+ ctx.beginPath();
+ ctx.arc(vm.x, vm.y, vm.outerRadius, sA, eA);
+ ctx.arc(vm.x, vm.y, vm.innerRadius, eA, sA, true);
+ ctx.closePath();
+
+ ctx.lineWidth = vm.borderWidth * 2;
+ ctx.lineJoin = 'round';
+ } else {
+ ctx.lineWidth = vm.borderWidth;
+ ctx.lineJoin = 'bevel';
+ }
+
+ ctx.strokeStyle = vm.borderColor;
+ ctx.stroke();
+ }
+
+ ctx.restore();
+};
+
+// Code adapted from https://stackoverflow.com/a/43026361/544806
+Chart.pluginService.register({
+ beforeDraw: function (chart: any) {
+ if (chart.config.options.elements.center) {
+ //Get ctx from string
+ let ctx = chart.chart.ctx;
+ //Get options from the center object in options
+ const centerConfig = chart.config.options.elements.center;
+ const fontStyle = centerConfig.fontStyle || 'Noto Sans';
+ const txt = centerConfig.text;
+ const color = centerConfig.color || '#000';
+ const sidePadding = centerConfig.sidePadding || 20;
+ const sidePaddingCalculated = (sidePadding/100) * (chart.innerRadius * 2)
+ //Start with a base font of 30px
+ ctx.font = "12px " + fontStyle;
+
+ //Get the width of the string and also the width of the element minus 10 to give it 5px side padding
+ const stringWidth = ctx.measureText(txt).width;
+ const elementWidth = (chart.innerRadius * 2) - sidePaddingCalculated;
+
+ // Find out how much the font can grow in width.
+ const widthRatio = elementWidth / stringWidth;
+ const newFontSize = Math.floor(30 * widthRatio);
+ const elementHeight = (chart.innerRadius * 2);
+
+ // Pick a new font size so it will not be larger than the height of label.
+ const fontSizeToUse = Math.min(newFontSize, elementHeight, centerConfig.maxFontSize);
+
+ // Set font settings to draw it correctly.
+ ctx.textAlign = 'center';
+ ctx.textBaseline = 'middle';
+ const centerX = ((chart.chartArea.left + chart.chartArea.right) / 2);
+ const centerY = ((chart.chartArea.top + chart.chartArea.bottom) / 2);
+ ctx.font = fontSizeToUse+"px " + fontStyle;
+ ctx.fillStyle = color;
+
+ // Draw text in center
+ ctx.fillText(txt, centerX, centerY);
+ }
+ }
+});
+
export class PatternPieChart extends React.Component<PatternPieChartProps> {
- public static defaultProps = {
- borderWidth: 1,
- labelFontSize: 12,
- paddingTop: 0,
- paddingBottom: 0,
- paddingLeft: 0,
- paddingRight: 0,
- };
render() {
- let { height, data, labelFontSize } = this.props;
- const theme = {
- labels: {
- text: {
- fontSize: labelFontSize
- }
- }
- };
+ let { data, labelFontSize } = this.props;
const colors = data.map(p => p.color ? p.color: defaultChartColor);
+ const totalValue = data.map(p => p.value).reduce((ans, v) => ans + v);
return (
- <Doughnut data={(canvas: any) => {
+ <DChart data={() => {
return {
datasets: [{
data: data.map(p => p.value),
backgroundColor: colors,
borderWidth: data.map(() => this.props.borderWidth),
+ borderColor: colors.map(c => Color(c).darken(0.1).string()),
hoverBorderWidth: data.map(() => this.props.borderWidth),
- hoverBorderColor: colors.map(c => Color(c).darken(0.2).string())
+ hoverBorderColor: colors.map(c => Color(c).darken(0.3).string())
}],
labels: data.map(p => p.name)
};
}} options={{
+ elements: {
+ center: {
+ text: `${totalValue.toFixed(2)} hr`,
+ color: theme.palette.text.secondary,
+ maxFontSize: 20,
+ sidePadding: 50
+ }
+ },
tooltips: {
callbacks: {
- label: (item: any, data: any) => (
+ label: (item: { index: number, datasetIndex: number },
+ data: { labels: string[], datasets: { data: number[] }[] }) => {
+ const v = data.datasets[item.datasetIndex].data[item.index];
+ return (
`${data.labels[item.index]}: ` +
- `${data.datasets[item.datasetIndex].data[item.index].toFixed(2)} hr`
- )
+ `${v.toFixed(2)} hr (${(v / totalValue * 100).toFixed(2)} %)`
+ );
+ }
}
},
plugins: {
labels: {
- render: (args: any) => `${args.value.toFixed(2)} hr`,
- fontColor: (data: any) => {
+ render: (args: { value: number }) => `${args.value.toFixed(2)} hr`,
+ fontColor: (data: { index: number, dataset: { backgroundColor: string[] } }) => {
var rgb = Color(data.dataset.backgroundColor[data.index]).rgb().object();
- var threshold = 140;
+ var threshold = 150;
var luminance = 0.299 * rgb.r + 0.587 * rgb.g + 0.114 * rgb.b;
return luminance > threshold ? 'black' : 'white';
},
@@ -99,7 +221,55 @@ export class PatternPieChart extends React.Component<PatternPieChartProps> {
}
}
-export const StyledPatternPieChart = withStyles(styles)(PatternPieChart);
+interface DoughnutChartProps extends PatternPieChartProps {
+ height: number
+}
+
+class _DoughnutChart extends React.Component<DoughnutChartProps> {
+ public static defaultProps = {
+ height: 300,
+ borderWidth: 1,
+ labelFontSize: 12,
+ paddingTop: 0,
+ paddingBottom: 0,
+ paddingLeft: 0,
+ paddingRight: 0,
+ };
+ render() {
+ const h = this.props.height;
+ const ih = h * (1 - 0.05 - 0.20);
+ return ((this.props.data.some(dd => dd.value > 1e-3) &&
+ <div style={{height: h}}>
+ <PatternPieChart {...this.props} />
+ </div>) ||
+ <div style={{
+ marginTop: 0.05 * h,
+ marginBottom: 0.20 * h,
+ textAlign: 'center'
+ }}>
+ <div style={{
+ position: 'relative',
+ height: ih,
+ display: 'inline-block'
+ }}>
+ <Doughnut style={{
+ height: '100%'
+ }} />
+ <div style={{
+ position: 'absolute',
+ bottom: -ih * 0.15,
+ left: ih * 0.5 - 73,
+ }}>
+ <Typography variant="subtitle1" align="center" color="textSecondary">
+ No matching events.
+ </Typography>
+ </div>
+ </div>
+ </div>);
+ }
+}
+
+export const DoughnutChart = withStyles(styles)(_DoughnutChart);
type DoublePieChartProps = {
classes: {
@@ -112,12 +282,12 @@ type DoublePieChartProps = {
function DoublePieChart(props: DoublePieChartProps) {
return (
- <Grid container spacing={0}>
- <Grid item md={12} lg={12} style={{height: 300}}>
- <StyledPatternPieChart data={props.patternGraphData} />
+ <Grid container spacing={16}>
+ <Grid item md={12} sm={6} xs={12}>
+ <DoughnutChart height={300} data={props.patternGraphData} />
</Grid>
- <Grid item md={12} lg={12} style={{height: 300}}>
- <StyledPatternPieChart data={props.calendarGraphData} />
+ <Grid item md={12} sm={6} xs={12}>
+ <DoughnutChart height={300} data={props.calendarGraphData} />
</Grid>
</Grid>);
}
diff --git a/src/Donut.tsx b/src/Doughnut.tsx
index 00d472a..09844b5 100644
--- a/src/Donut.tsx
+++ b/src/Doughnut.tsx
@@ -9,7 +9,7 @@ const styles = {
st6: {fill: '#F69D98'},
};
-function Donut(props: {
+function Doughnut(props: {
style: {[key: string]: string | number },
classes: {
st0: string,
@@ -157,4 +157,4 @@ function Donut(props: {
</svg>);
}
-export default withStyles(styles)(Donut);
+export default withStyles(styles)(Doughnut);
diff --git a/src/background.ts b/src/background.ts
index 84f1f43..a9fed12 100644
--- a/src/background.ts
+++ b/src/background.ts
@@ -370,9 +370,6 @@ chrome.tabs.onCreated.addListener(function(tab) {
}
});
-chrome.runtime.onInstalled.addListener(async () => {
- try {
- await auth.logout();
- calData = {};
- } catch (_) {}
+chrome.runtime.onInstalled.addListener(() => {
+ chrome.tabs.create({ url: "index.html" });
});
diff --git a/src/popup.tsx b/src/popup.tsx
index d0f714b..81b42ae 100644
--- a/src/popup.tsx
+++ b/src/popup.tsx
@@ -11,7 +11,7 @@ import CircularProgress from '@material-ui/core/CircularProgress';
import Logo from './Logo';
import { theme } from './theme';
-import { StyledPatternPieChart } from './Chart';
+import { DoughnutChart } from './Chart';
import { MsgType, MsgClient } from './msg';
import { GraphData } from './graph';
import moment from 'moment';
@@ -47,7 +47,7 @@ type PopupProps = {
}
};
-class Popup extends React.Component<PopupProps> {
+class _Popup extends React.Component<PopupProps> {
msgClient: MsgClient;
state = {
patternGraphData: [] as GraphData[],
@@ -108,7 +108,7 @@ class Popup extends React.Component<PopupProps> {
${moment(d.end).format('ddd, MMM Do, YYYY')}`}
</Typography>
{(d.data.some(dd => dd.value > 1e-3) &&
- <div style={{height: 300}}><StyledPatternPieChart data={d.data} /></div>) ||
+ <DoughnutChart height={300} data={d.data} />) ||
<Typography variant="subtitle1" align="center" color="textSecondary">
No matching events.
</Typography>}
@@ -124,6 +124,6 @@ class Popup extends React.Component<PopupProps> {
}
}
-const StyledPopup = withStyles(styles)(Popup);
+const Popup = withStyles(styles)(_Popup);
-ReactDOM.render(<StyledPopup />, document.getElementById('root'));
+ReactDOM.render(<Popup />, document.getElementById('root'));
diff --git a/src/tab.tsx b/src/tab.tsx
index 86e7af3..6cbd36b 100644
--- a/src/tab.tsx
+++ b/src/tab.tsx
@@ -10,9 +10,8 @@ import Grid from '@material-ui/core/Grid';
import CircularProgress from '@material-ui/core/CircularProgress';
import Logo from './Logo';
-import Donut from './Donut';
import { theme } from './theme';
-import { StyledPatternPieChart } from './Chart';
+import { DoughnutChart } from './Chart';
import { MsgType, MsgClient } from './msg';
import { GraphData } from './graph';
import moment from 'moment';
@@ -45,7 +44,7 @@ type TabProps = {
};
-class Tab extends React.Component<TabProps> {
+class _Tab extends React.Component<TabProps> {
msgClient: MsgClient;
state = {
patternGraphData: [] as GraphData[],
@@ -106,40 +105,13 @@ class Tab extends React.Component<TabProps> {
{`${moment(d.start).format('ddd, MMM Do, YYYY')} -
${moment(d.end).format('ddd, MMM Do, YYYY')}`}
</Typography>
- {(d.data.some(dd => dd.value > 1e-3) &&
- <div style={{height: 400}}>
- <StyledPatternPieChart
+ <DoughnutChart
data={d.data}
height={400}
borderWidth={2}
paddingTop={20}
paddingBottom={50}
labelFontSize={14} />
- </div>) ||
- <div style={{
- marginTop: 20,
- marginBottom: 60,
- textAlign: 'center'
- }}>
- <div style={{
- position: 'relative',
- height: 270,
- display: 'inline-block'
- }}>
- <Donut style={{
- height: '100%'
- }} />
- <div style={{
- position: 'absolute',
- bottom: -40,
- left: 60,
- }}>
- <Typography variant="subtitle1" align="center" color="textSecondary">
- No matching events.
- </Typography>
- </div>
- </div>
- </div>}
</Grid>
))) || (
<div className={classes.loading}><CircularProgress color="primary" /></div>
@@ -152,6 +124,6 @@ class Tab extends React.Component<TabProps> {
}
}
-const StyledTab = withStyles(styles)(Tab);
+const Tab = withStyles(styles)(_Tab);
-ReactDOM.render(<StyledTab />, document.getElementById('root'));
+ReactDOM.render(<Tab />, document.getElementById('root'));