376 lines
12 KiB
JavaScript
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);
|
|
},
|
|
},
|
|
});
|