Files

376 lines
12 KiB
JavaScript

import Ember from 'ember';
import Moment from 'moment';
import { extendMoment } from 'moment-range';
import Resizable from './chart/resizable';
import FieldsByUnits from 'flaredown/mixins/fields-by-units';
import DatesRetriever from 'flaredown/mixins/chart/dates-retriever';
import * as d3 from "d3";
const moment = extendMoment(Moment);
const {
get,
set,
computed,
observer,
Component,
getProperties,
setProperties,
run: {
debounce,
scheduleOnce,
},
inject: {
service,
},
computed: {
alias,
},
} = Ember;
export default Component.extend(Resizable, FieldsByUnits, DatesRetriever, {
chartSelectedDates: service(),
classNames: ['health-chart'],
checkins: [],
trackables: [],
flatHeight: 30,
serieHeight: 75,
seriePadding: 20,
pixelsPerDate: 32,
timelineHeight: 25,
lastChartOffset: 0,
lastChartHeight: 0,
centeredDate: alias('chartSelectedDates.centeredDate'),
hiddenCharts: alias('chartsVisibilityService.hiddenCharts'),
pressureUnits: alias('session.currentUser.profile.pressureUnits'),
timelineLength: alias('timeline.length'),
visibilityFilter: alias('chartsVisibilityService.visibilityFilter'),
updateTrackables: observer(
'centeredDate',
'chartLoaded',
'chartsVisibilityService.payload.tags.@each.visible',
'chartsVisibilityService.payload.foods.@each.visible',
'chartsVisibilityService.payload.symptoms.@each.visible',
'chartsVisibilityService.payload.conditions.@each.visible',
'chartsVisibilityService.payload.treatments.@each.visible',
'chartsVisibilityService.payload.weathersMeasures.@each.visible',
'chartsVisibilityService.payload.harveyBradshawIndices.@each.visible',
function() {
debounce(this, this.fetchDataChart, 1000);
}
),
showChart: observer('chartToShow', function() {
const { chartToShow, chartsVisibilityService } = getProperties(this, 'chartToShow', 'chartsVisibilityService');
chartsVisibilityService.setVisibility(true, chartToShow.category, chartToShow.label);
}),
isChartEnablerDisabled: computed('hiddenCharts.length', function() {
return get(this, 'hiddenCharts.length') === 0;
}),
chartEnablerPlaceholder: computed('isChartEnablerDisabled', function() {
return get(this, 'isChartEnablerDisabled') ? "No charts to add" : "Add a chart";
}),
seriesLength: computed('series.weathersMeasures.length', 'trackables.length', function() {
return get(this, 'trackables.length') + get(this, 'series.weathersMeasures.length');
}),
seriesWidth: computed('SVGWidth', function() {
return get(this, 'SVGWidth') * 2;
}),
totalSeriesHeight: computed('lastChartOffset', 'lastChartHeight', function() {
const {
lastChartOffset,
lastChartHeight,
seriePadding,
} = getProperties(this, 'lastChartOffset', 'lastChartHeight', 'seriePadding');
return lastChartOffset + lastChartHeight + seriePadding;
}),
timeline: computed('checkins', 'startAt', 'endAt', function() {
let timeline = Ember.A();
moment.range(get(this, 'startAtWithCache'), get(this, 'endAtWithCache')).by('days', function(day) {
timeline.pushObject(
d3.time.format('%Y-%m-%d').parse(day.format("YYYY-MM-DD"))
);
});
return timeline;
}),
SVGHeight: computed('timelineLength', 'totalSeriesHeight', function() {
if(Ember.isPresent(get(this, 'totalSeriesHeight'))) {
return get(this, 'totalSeriesHeight') + get(this, 'timelineHeight') + get(this, 'seriePadding');
} else {
return get(this, 'timelineHeight') + get(this, 'seriePadding');
}
}),
SVGWidth: computed('checkins',function() {
return this.$().width();
}),
observeFilterAndTrackables: observer(
'trackables',
'pressureUnits',
'chartsVisibilityService.payload.tags.@each.visible',
'chartsVisibilityService.payload.foods.@each.visible',
'chartsVisibilityService.payload.symptoms.@each.visible',
'chartsVisibilityService.payload.conditions.@each.visible',
'chartsVisibilityService.payload.treatments.@each.visible',
'chartsVisibilityService.payload.weathersMeasures.@each.visible',
'chartsVisibilityService.payload.harveyBradshawIndices.@each.visible',
function() {
const {
flatHeight,
serieHeight,
seriePadding,
visibilityFilter,
} = getProperties(this, 'flatHeight', 'serieHeight', 'seriePadding', 'visibilityFilter');
let lastChartHeight = serieHeight;
let chartOffset = 0 - lastChartHeight - seriePadding;
let series = this.seriesWithBlanks(
this.unpositionedSeries(visibilityFilter),
visibilityFilter
);
series.conditions.forEach(item => {
item.chartOffset = chartOffset += lastChartHeight + seriePadding;
});
series.symptoms.forEach(item => {
item.chartOffset = chartOffset += lastChartHeight + seriePadding;
});
series.treatments.forEach((item, index) => {
item.chartOffset = chartOffset += (index === 0 ? serieHeight : flatHeight) + seriePadding;
});
series.tags.forEach((item, index) => {
let previousHeight = series.treatments.length === 0 && index === 0 ? serieHeight : flatHeight;
item.chartOffset = chartOffset += previousHeight + seriePadding;
});
series.foods.forEach((item, index) => {
let previousHeight = series.tags.length === 0 && series.treatments.length === 0 && index === 0 ?
serieHeight
:
flatHeight;
item.chartOffset = chartOffset += previousHeight + seriePadding;
});
if (series.treatments.length || series.tags.length || series.foods.length) {
lastChartHeight = flatHeight;
}
const hbiCategory = visibilityFilter.harveyBradshawIndices;
if (hbiCategory && hbiCategory['Harvey Bradshaw Index']) {
series.harveyBradshawIndices.pushObject({
name: 'Harvey Bradshaw Index',
field: 'score',
chartOffset: chartOffset += lastChartHeight + seriePadding,
});
lastChartHeight = serieHeight;
}
const weatherCategory = visibilityFilter.weathersMeasures;
if (weatherCategory && weatherCategory['Avg daily humidity']) {
series.weathersMeasures.pushObject({
name: 'Avg daily humidity',
unit: '%',
field: 'humidity',
chartOffset: chartOffset += lastChartHeight + seriePadding,
});
lastChartHeight = serieHeight;
}
if (weatherCategory && weatherCategory['Avg daily atmospheric pressure']) {
series.weathersMeasures.pushObject({
name: 'Avg daily atmospheric pressure',
unit: get(this, 'pressureUnits'),
field: this.pressureFieldByUnits(get(this, 'pressureUnits')),
chartOffset: chartOffset += lastChartHeight + seriePadding,
});
lastChartHeight = serieHeight;
}
set(this, 'series', series);
set(this, 'lastChartOffset', chartOffset);
set(this, 'lastChartHeight', lastChartHeight);
}
),
unpositionedSeries(visibilityFilter) {
let series = {
conditions: [],
symptoms: [],
treatments: [],
tags: [],
foods: [],
harveyBradshawIndices: [],
weathersMeasures: [],
};
get(this, 'trackables').forEach(item => {
let name = get(item, 'name');
let category = get(item, 'constructor.modelName').pluralize();
let visibleCategory = visibilityFilter[category];
if (visibleCategory && visibleCategory[name]) {
series[category].pushObject({ chartOffset: 0, model: item });
}
});
return series;
},
seriesWithBlanks(series, visibilityFilter) {
let result = {
conditions: [],
symptoms: [],
treatments: [],
tags: [],
foods: [],
harveyBradshawIndices: [],
weathersMeasures: [],
};
Object.keys(visibilityFilter).forEach(categoryName => {
const category = get(visibilityFilter, categoryName);
Object.keys(category).forEach(chartName => {
if (category[chartName] && series[categoryName]) {
const chartFromSeries = series[categoryName].length && series[categoryName].findBy('model.name', chartName);
result[categoryName].pushObject(chartFromSeries || {
blank: true,
chartOffset: 0,
model: Ember.Object.create({
name: chartName,
fillClass: 'colorable-fill-35',
strokeClass: 'colorable-stroke-35',
}),
});
}
});
});
return result;
},
didInsertElement() {
this._super(...arguments);
scheduleOnce('afterRender', this, this.fetchDataChart);
},
fetchDataChart() {
const { endAt, startAt } = getProperties(this, 'endAt', 'startAt');
const checkins = this.peekSortedCheckins();
let payloadVersion = get(this, 'chartsVisibilityService.payloadVersion');
let payloadDirection = get(this, 'chartsVisibilityService.payloadDirection');
let oldPayloadVersion = get(this, 'oldPayloadVersion');
if (
(!oldPayloadVersion || !payloadDirection || payloadVersion === oldPayloadVersion) &&
checkins.length &&
endAt.isSameOrBefore(checkins.get('lastObject.date'), 'day') &&
startAt.isSameOrAfter(checkins.get('firstObject.date'), 'day')
) {
return this.isDestroyed ||
(this.setChartsData() && setProperties(this, { chartLoaded: true, oldPayloadVersion: payloadVersion }));
} else {
const fetchOnlyQuery = get(this, 'chartsVisibilityService.fetchOnlyQuery');
if (!Object.keys(fetchOnlyQuery).length) {
return;
}
const { endAtWithCache, startAtWithCache } = getProperties(this, 'endAtWithCache', 'startAtWithCache');
const query = {
id: 'health',
end_at: endAtWithCache.format("YYYY-MM-DD"),
includes: fetchOnlyQuery,
start_at: startAtWithCache.format("YYYY-MM-DD"),
};
return this
.store
.queryRecord('chart', query)
.then(() => this.setChartsData())
.then(() => this.isDestroyed || setProperties(this, { chartLoaded: true, oldPayloadVersion: payloadVersion }));
}
},
setChartsData() {
const tags = this.store.peekAll('tag').toArray();
const foods = this.store.peekAll('food').toArray();
const checkins = this.peekSortedCheckins();
const symptoms = this.store.peekAll('symptom').toArray();
const conditions = this.store.peekAll('condition').toArray();
const treatments = this.store.peekAll('treatment').toArray();
const harveyBradshawIndices = this.store.peekAll('harveyBradshawIndex').toArray();
const trackables = [...conditions, ...symptoms, ...treatments, ...tags, ...foods, ...harveyBradshawIndices];
return this.isDestroyed || setProperties(this, {checkins, trackables});
},
peekSortedCheckins() {
return this
.store
.peekAll('checkin')
.toArray()
.sort(
(a, b) => moment(get(a, 'date')).diff(get(b, 'date'), 'days')
);
},
onResizeEnd() {
this.fetchDataChart();
},
actions: {
navigate(days) {
let centeredDate = get(this, 'centeredDate');
centeredDate = centeredDate ? moment(centeredDate) : get(this, 'endAt').subtract(get(this, 'daysRadius'), 'days');
set(this, 'centeredDate', centeredDate.add(days, 'days'));
},
setCurrentDate(date) {
get(this, 'onDateClicked')(date);
},
openInfoWindow(date, xPosition) {
set(this, 'xPosition', xPosition);
set(this, 'openInfoWindow', true);
},
closeInfoWindow() {
set(this, 'xPosition', null);
set(this, 'openInfoWindow', false);
},
},
});