Skip to content
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 ChartJS BarCharts to Dashboard "Chart" tab #997

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions package.cordovabuild.json
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,9 @@
"angular-ui-router": "0.2.13",
"animate.css": "^3.5.2",
"bottleneck": "^2.19.5",
"chart.js": "^4.3.0",
"chartjs-adapter-luxon": "^1.3.1",
"chartjs-plugin-annotation": "^3.0.1",
"cordova-android": "11.0.0",
"cordova-ios": "6.2.0",
"cordova-plugin-advanced-http": "3.3.1",
Expand Down Expand Up @@ -140,6 +143,7 @@
"jquery": "^3.1.0",
"klaw-sync": "^6.0.0",
"leaflet": "^1.9.4",
"luxon": "^3.3.0",
"messageformat": "^2.3.0",
"moment": "^2.29.4",
"moment-timezone": "^0.5.43",
Expand All @@ -149,6 +153,7 @@
"phonegap-plugin-barcodescanner": "git+https://github.com/phonegap/phonegap-plugin-barcodescanner#v8.1.0",
"prop-types": "^15.8.1",
"react": "^18.2.*",
"react-chartjs-2": "^5.2.0",
"react-dom": "^18.2.*",
"react-i18next": "^12.3.1",
"react-native-paper": "^5.8.0",
Expand Down
5 changes: 5 additions & 0 deletions package.serve.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@
"angular-ui-router": "0.2.13",
"animate.css": "^3.5.2",
"bottleneck": "^2.19.5",
"chart.js": "^4.3.0",
"chartjs-adapter-luxon": "^1.3.1",
"chartjs-plugin-annotation": "^3.0.1",
"core-js": "^2.5.7",
"enketo-core": "^6.1.7",
"fast-xml-parser": "^4.2.2",
Expand All @@ -66,6 +69,7 @@
"jquery": "^3.1.0",
"klaw-sync": "^6.0.0",
"leaflet": "^1.9.4",
"luxon": "^3.3.0",
shankari marked this conversation as resolved.
Show resolved Hide resolved
"messageformat": "^2.3.0",
"moment": "^2.29.4",
"moment-timezone": "^0.5.43",
Expand All @@ -74,6 +78,7 @@
"nvd3": "^1.8.6",
"prop-types": "^15.8.1",
"react": "^18.2.*",
"react-chartjs-2": "^5.2.0",
"react-dom": "^18.2.*",
"react-i18next": "^12.3.1",
"react-native-paper": "^5.8.0",
Expand Down
1 change: 1 addition & 0 deletions www/js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import 'angular-translate-loader-static-files';

import 'moment';
import 'moment-timezone';
import 'chartjs-adapter-luxon';

import i18next from 'i18next';
import { initReactI18next } from 'react-i18next';
Expand Down
201 changes: 201 additions & 0 deletions www/js/components/BarChart.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@

import React, { useRef, useState } from 'react';
import { array, string, bool } from 'prop-types';
import { angularize } from '../angular-react-helper';
import { View } from 'react-native';
import { useTheme } from 'react-native-paper';
import { Chart, CategoryScale, LinearScale, BarElement, Title, Tooltip, Legend, TimeScale } from 'chart.js';
import { Bar } from 'react-chartjs-2';
import Annotation, { AnnotationOptions } from 'chartjs-plugin-annotation';

Chart.register(
CategoryScale,
LinearScale,
TimeScale,
BarElement,
Title,
Tooltip,
shankari marked this conversation as resolved.
Show resolved Hide resolved
Legend,
Annotation,
);

const BarChart = ({ chartData, axisTitle, lineAnnotations=null, isHorizontal=false }) => {

const { colors } = useTheme();
const [ numVisibleDatasets, setNumVisibleDatasets ] = useState(1);

const barChartRef = useRef<Chart>(null);

const defaultPalette = [
'#c95465', // red oklch(60% 0.15 14)
'#4a71b1', // blue oklch(55% 0.11 260)
'#d2824e', // orange oklch(68% 0.12 52)
'#856b5d', // brown oklch(55% 0.04 50)
'#59894f', // green oklch(58% 0.1 140)
'#e0cc55', // yellow oklch(84% 0.14 100)
'#b273ac', // purple oklch(64% 0.11 330)
'#f09da6', // pink oklch(78% 0.1 12)
'#b3aca8', // grey oklch(75% 0.01 55)
'#80afad', // teal oklch(72% 0.05 192)
shankari marked this conversation as resolved.
Show resolved Hide resolved
]

const indexAxis = isHorizontal ? 'y' : 'x';

function getChartHeight() {
/* when horizontal charts have more data, they should get taller
so they don't look squished */
if (isHorizontal) {
// 'ideal' chart height is based on the number of datasets and number of unique index values
const uniqueIndexVals = [];
chartData.forEach(e => e.records.forEach(r => {
if (!uniqueIndexVals.includes(r[indexAxis])) uniqueIndexVals.push(r[indexAxis]);
}));
const numIndexVals = uniqueIndexVals.length;
const idealChartHeight = numVisibleDatasets * numIndexVals * 8;

/* each index val should be at least 20px tall for visibility,
and the graph itself should be at least 250px tall */
const minChartHeight = Math.max(numIndexVals * 20, 250);

// return whichever is greater
return { height: Math.max(idealChartHeight, minChartHeight) };
}
// vertical charts will just match the parent container
return { height: '100%' };
}

return (
<View style={[getChartHeight(), {padding: 12}]}>
<Bar ref={barChartRef}
data={{
datasets: chartData.map((d, i) => ({
label: d.label,
data: d.records,
// cycle through the default palette, repeat if necessary
backgroundColor: defaultPalette[i % defaultPalette.length],
}))
}}
options={{
indexAxis: indexAxis,
responsive: true,
maintainAspectRatio: false,
resizeDelay: 1,
scales: {
...(isHorizontal ? {
y: {
offset: true,
type: 'time',
adapters: {
date: { zone: 'utc' },
},
time: {
shankari marked this conversation as resolved.
Show resolved Hide resolved
unit: 'day',
tooltipFormat: 'DDD', // Luxon "localized date with full month": e.g. August 6, 2014
},
beforeUpdate: (axis) => {
setNumVisibleDatasets(axis.chart.getVisibleDatasetCount())
},
reverse: true,
},
x: {
title: { display: true, text: axisTitle },
},
} : {
x: {
offset: true,
type: 'time',
adapters: {
date: { zone: 'utc' },
},
time: {
unit: 'day',
tooltipFormat: 'DDD', // Luxon "localized date with full month": e.g. August 6, 2014
},
},
y: {
title: { display: true, text: axisTitle },
},
}),
},
plugins: {
...(lineAnnotations?.length > 0 && {
annotation: {
annotations: lineAnnotations.map((a, i) => ({
type: 'line',
label: {
display: true,
padding: { x: 3, y: 1 },
borderRadius: 0,
backgroundColor: 'rgba(0,0,0,.7)',
color: 'rgba(255,255,255,1)',
font: { size: 10 },
position: 'start',
content: a.label,
},
...(isHorizontal ? { xMin: a.value, xMax: a.value }
: { yMin: a.value, yMax: a.value }),
borderColor: colors.onBackground,
borderWidth: 2,
borderDash: [3, 3],
} satisfies AnnotationOptions)),
}
}),
}
}} />
</View>
)
}

BarChart.propTypes = {
chartData: array,
axisTitle: string,
lineAnnotations: array,
isHorizontal: bool,
};

angularize(BarChart, 'BarChart', 'emission.main.barchart');
export default BarChart;

// const sampleAnnotations = [
// { value: 35, label: 'Target1' },
// { value: 65, label: 'Target2' },
// ];

// const sampleChartData = [
// {
// label: 'Primary',
// records: [
// { x: moment('2023-06-20'), y: 20 },
// { x: moment('2023-06-21'), y: 30 },
// { x: moment('2023-06-23'), y: 80 },
// { x: moment('2023-06-24'), y: 40 },
// ],
// },
// {
// label: 'Secondary',
// records: [
// { x: moment('2023-06-21'), y: 10 },
// { x: moment('2023-06-22'), y: 50 },
// { x: moment('2023-06-23'), y: 30 },
// { x: moment('2023-06-25'), y: 40 },
// ],
// },
// {
// label: 'Tertiary',
// records: [
// { x: moment('2023-06-20'), y: 30 },
// { x: moment('2023-06-22'), y: 40 },
// { x: moment('2023-06-24'), y: 10 },
// { x: moment('2023-06-25'), y: 60 },
// ],
// },
// {
// label: 'Quaternary',
// records: [
// { x: moment('2023-06-22'), y: 10 },
// { x: moment('2023-06-23'), y: 20 },
// { x: moment('2023-06-24'), y: 30 },
// { x: moment('2023-06-25'), y: 40 },
// ],
// },
// ];
13 changes: 6 additions & 7 deletions www/js/metrics.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import angular from 'angular';
import 'nvd3';
import BarChart from './components/BarChart';

angular.module('emission.main.metrics',['emission.services',
'ionic-datepicker',
Expand All @@ -10,7 +11,8 @@ angular.module('emission.main.metrics',['emission.services',
'emission.main.metrics.mappings',
'emission.stats.clientstats',
'emission.plugin.kvstore',
'emission.plugin.logger'])
'emission.plugin.logger',
BarChart.module])

.controller('MetricsCtrl', function($scope, $ionicActionSheet, $ionicLoading,
ClientStats, CommHelper, $window, $ionicPopup,
Expand Down Expand Up @@ -1083,13 +1085,10 @@ angular.module('emission.main.metrics',['emission.services',
let modeStatList = modeMap["values"];
let formattedModeStatList = modeStatList.map((modeStat) => {
let [formatVal, unit, stringRep] = formatter(modeStat[1]);
let copiedModeStat = angular.copy(modeStat);
copiedModeStat[1] = formatVal;
copiedModeStat.push(unit);
copiedModeStat.push(stringRep);
return copiedModeStat;
// horizontal graphs: date on y axis and value on x axis
return { y: modeStat[0] * 1000, x: formatVal };
});
formattedModeList.push({key: currMode, values: formattedModeStatList});
formattedModeList.push({label: currMode, records: formattedModeStatList});
});
return formattedModeList;
}
Expand Down
12 changes: 6 additions & 6 deletions www/templates/main-metrics.html
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,10 @@
</div>
<div ng-if="uictrl.showResult">
<div ng-if="uictrl.showChart">
<nvd3 ng-if="uictrl.showTrips" options="countOptions" data="data.count" style="padding: 10px;"></nvd3>
<nvd3 ng-if="uictrl.showDistance" options="distanceOptions" data="data.distance" style="padding: 10px;"></nvd3>
<nvd3 ng-if="uictrl.showDuration" options="durationOptions" data="data.duration" style="padding: 10px;"></nvd3>
<nvd3 ng-if="uictrl.showSpeed" options="speedOptions" data="data.mean_speed" style="padding: 10px;"></nvd3>
<bar-chart ng-if="uictrl.showDistance" chart-data="data.distance" axis-title="distanceOptions.chart.yAxis.axisLabel" is-horizontal="true" style="padding: 10px;"></bar-chart>
<bar-chart ng-if="uictrl.showTrips" chart-data="data.count" axis-title="countOptions.chart.yAxis.axisLabel" is-horizontal="true" style="padding: 10px;"></bar-chart>
<bar-chart ng-if="uictrl.showDuration" chart-data="data.duration" axis-title="durationOptions.chart.yAxis.axisLabel" is-horizontal="true" style="padding: 10px;"></bar-chart>
<bar-chart ng-if="uictrl.showSpeed" chart-data="data.mean_speed" axis-title="speedOptions.chart.yAxis.axisLabel" is-horizontal="true" style="padding: 10px;"></bar-chart>
</div>
<div ng-if="uictrl.showSummary" overflow-scroll="true">
<div ng-class="changeFootprintCardHeight()" id="dashboard-footprint" class="card">
Expand Down Expand Up @@ -105,8 +105,8 @@ <h4 class="dashboard-headers" ng-i18next>{{'main-metrics.calories'}} </h4>
<div ng-class="currentQueryForCalorie()">
<div class="user-calorie"><rangedisplay range="caloriesData.userCalories"></rangedisplay> kcal</div>
<div class="calorie-change" ng-if="foodCompare == 'cookie'">{{'main-metrics.equals-cookies' | i18next:{count: numberOfCookies.low} }}</div>
<div class="calorie-change" ng-if="foodCompare == 'iceCream'">{{'main-metrics.equals-icecream' | i18next:{icecream: numberOfIceCreams.low} }}</div>
<div class="calorie-change" ng-if="foodCompare == 'banana'">{{'main-metrics.equals-bananas' | i18next:{bananas: numberOfBananas.low} }}</div>
<div class="calorie-change" ng-if="foodCompare == 'iceCream'">{{'main-metrics.equals-icecream' | i18next:{count: numberOfIceCreams.low} }}</div>
<div class="calorie-change" ng-if="foodCompare == 'banana'">{{'main-metrics.equals-bananas' | i18next:{count: numberOfBananas.low} }}</div>
<diffdisplay ng-if="caloriesData.greaterLesserPct" change="caloriesData.greaterLesserPct"></diffdisplay>
</div>
</div>
Expand Down