added 260907 version of worker toolkit
This commit is contained in:
@@ -0,0 +1,225 @@
|
||||
import Ember from 'ember';
|
||||
import moment from 'moment';
|
||||
import * as d3 from "d3";
|
||||
|
||||
const {
|
||||
get,
|
||||
set,
|
||||
computed,
|
||||
observer,
|
||||
Component,
|
||||
setProperties,
|
||||
A,
|
||||
} = Ember;
|
||||
|
||||
export default Component.extend({
|
||||
classNames: ['flaredown-svg-group'],
|
||||
|
||||
svg: null,
|
||||
data: null,
|
||||
|
||||
svgChartWidth: 500,
|
||||
svgLineAreaHeight: 150,
|
||||
svgLineOffset: 20,
|
||||
svgLineHeight: 3,
|
||||
startAt: null,
|
||||
endAt: null,
|
||||
margin: { top: 20, right: 0, bottom: 5, left: 0 }, //margin for charts
|
||||
backgroundMargin: { right: 10, left: 10 , top: 0 }, // for init-svg
|
||||
colorIds: A(),
|
||||
hasLines: false,
|
||||
svgInitial: false,
|
||||
|
||||
init(){
|
||||
this._super(...arguments);
|
||||
|
||||
const xScale = d3.time.scale().range([0, get(this, 'svgWidth')]);
|
||||
const yScaleStatic = d3.scale.linear().range([get(this, 'svgLineAreaHeight'), 0]);
|
||||
|
||||
setProperties(this, { xScale, yScaleStatic });
|
||||
},
|
||||
|
||||
didInsertElement() {
|
||||
this._super(...arguments);
|
||||
|
||||
const svgCanvas = d3.select(this.element).select('svg');
|
||||
const svgBackgroundArea = svgCanvas.append('g').attr('class', 'background-area');
|
||||
const svg = svgCanvas.append('g').attr('class', 'offset');
|
||||
|
||||
const margin = get(this, 'margin');
|
||||
svg.attr("transform", "translate(" + margin.left + "," + margin.top + ")"); // svg references to g
|
||||
|
||||
const resize = () => {
|
||||
let svgSelection = this.$();
|
||||
if(svgSelection) {
|
||||
const width = this.$().width();
|
||||
const backgroundMargin = get(this, 'backgroundMargin');
|
||||
|
||||
set(this, 'svgWidth', width);
|
||||
set(this, 'svgChartWidth', width - backgroundMargin.left - backgroundMargin.right);
|
||||
}
|
||||
};
|
||||
|
||||
setProperties(this, { svgBackgroundArea, svg });
|
||||
|
||||
window.addEventListener("resize", resize);
|
||||
|
||||
resize();
|
||||
},
|
||||
|
||||
xScaleObserver: observer('svgWidth', 'svgChartWidth', function(){
|
||||
const svgInitial = get(this, 'svgInitial');
|
||||
const svgWidth = get(this, 'svgWidth');
|
||||
const width = svgInitial ? svgWidth - 2*get(this, 'backgroundMargin.left') : svgWidth;
|
||||
|
||||
get(this, 'xScale').range([0, width]);
|
||||
|
||||
this.renderBackground();
|
||||
|
||||
if(get(this, 'hasLines')) {
|
||||
this.renderYGrid();
|
||||
}
|
||||
}),
|
||||
|
||||
svgBackgroundObserver: observer('svgBackgroundArea', function() {
|
||||
const svgBackgroundArea = get(this, 'svgBackgroundArea');
|
||||
|
||||
if(!svgBackgroundArea) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(svgBackgroundArea.select('rect').empty()) {
|
||||
svgBackgroundArea.append('rect').attr('class', 'svg-background');
|
||||
}
|
||||
}),
|
||||
|
||||
svgGroupObserver: observer('svg', function() {
|
||||
const svg = get(this, 'svg');
|
||||
|
||||
if(!svg) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(svg.select('.grid-area').empty()) {
|
||||
svg.append('g').attr('class', 'grid-area');
|
||||
}
|
||||
|
||||
if(svg.select('.lines-area').empty()) {
|
||||
const xOffset = get(this, 'svgInitial') ? get(this, 'backgroundMargin.left') : 0;
|
||||
|
||||
svg.append('g').attr('class', 'lines-area')
|
||||
.attr('transform', `translate(${xOffset},0)`);
|
||||
}
|
||||
|
||||
if(svg.select('.lines-dots-area').empty()) {
|
||||
const xOffset = get(this, 'svgInitial') ? get(this, 'backgroundMargin.left') : 0;
|
||||
|
||||
svg.append('g').attr('class', 'lines-dots-area')
|
||||
.attr('transform', `translate(${xOffset},0)`);
|
||||
}
|
||||
|
||||
if(svg.select('.dots-area').empty()) {
|
||||
const xOffset = get(this, 'svgInitial') ? get(this, 'backgroundMargin.left') : 0;
|
||||
|
||||
svg.append('g').attr('class', 'dots-area')
|
||||
.attr('transform', `translate(${xOffset},0)`);
|
||||
}
|
||||
}),
|
||||
|
||||
startAtOffset: computed('startAt', 'daysRangeOffset', function() {
|
||||
return moment(get(this, 'startAt')).subtract(get(this, 'daysRangeOffset'), 'days');
|
||||
}),
|
||||
|
||||
svgHeight: computed('data.series.[]', function() {
|
||||
let height = 0;
|
||||
let filteredSeries = A();
|
||||
const series = get(this, 'data.series') || [];
|
||||
|
||||
const dynamicSeries = series.filterBy('subtype', 'dynamic');
|
||||
const lines = series.filterBy('type', 'line')
|
||||
const markers = series.filterBy('type', 'marker');
|
||||
const margin = get(this, 'margin');
|
||||
|
||||
filteredSeries.pushObjects([lines, dynamicSeries, markers]).map((itemArray) => {
|
||||
return this.addChartAttributes(itemArray);
|
||||
});
|
||||
|
||||
if(lines.length > 0 || dynamicSeries.length > 0) {
|
||||
height += get(this, 'svgLineAreaHeight') + get(this, 'svgLineOffset');
|
||||
set(this, 'hasLines', true);
|
||||
} else {
|
||||
height += get(this, 'svgLineOffset');
|
||||
set(this, 'svgLineAreaHeight', 0);
|
||||
}
|
||||
|
||||
if(markers.length > 0) {
|
||||
markers.forEach(() => {
|
||||
height += get(this, 'svgLineHeight') + get(this, 'svgLineOffset');
|
||||
});
|
||||
}
|
||||
|
||||
get(this, 'xScale').domain([
|
||||
get(this, 'startAtOffset').startOf('day').toDate().getTime(),
|
||||
get(this, 'endAt').endOf('day').toDate().getTime(),
|
||||
]);
|
||||
|
||||
get(this, 'yScaleStatic').domain([0, 4]);
|
||||
|
||||
height += margin.top + margin.bottom;
|
||||
|
||||
return height || 0;
|
||||
}),
|
||||
|
||||
svgChartHeigth: computed('svgHeight', function() {
|
||||
const margin = get(this, 'margin');
|
||||
|
||||
return (get(this, 'svgHeight') - margin.top - margin.bottom);
|
||||
}),
|
||||
|
||||
addChartAttributes(item_array) {
|
||||
let index = get(item_array, 'firstObject.subtype') === 'dynamic' ? 10 : 0;
|
||||
|
||||
item_array.map((i) => {
|
||||
i.index = index;
|
||||
index += 1;
|
||||
})
|
||||
},
|
||||
|
||||
renderBackground() {
|
||||
const backgroundMargin = get(this, 'backgroundMargin');
|
||||
const svgBackgroundArea = get(this, 'svgBackgroundArea');
|
||||
|
||||
svgBackgroundArea.attr("transform", "translate(" + backgroundMargin.left + "," + backgroundMargin.top + ")");
|
||||
|
||||
svgBackgroundArea
|
||||
.select('rect')
|
||||
.attr('class', 'svg-background')
|
||||
.attr('width', get(this, 'svgChartWidth'))
|
||||
.attr('height', get(this, 'svgHeight'));
|
||||
},
|
||||
|
||||
renderYGrid() {
|
||||
const svg = get(this, 'svg');
|
||||
const yScaleGrid = get(this, 'yScaleStatic');
|
||||
const gridArea = svg.select('g.grid-area');
|
||||
const backgroundMargin = get(this, 'backgroundMargin')
|
||||
|
||||
let attr = {
|
||||
'class': 'yGrid',
|
||||
'x1' : backgroundMargin.left,
|
||||
'x2' : (get(this, 'svgWidth') - backgroundMargin.right),
|
||||
'y1' : (d) => { return yScaleGrid(d) },
|
||||
'y2' : (d) => { return yScaleGrid(d) },
|
||||
};
|
||||
|
||||
let grid = gridArea.selectAll('.yGrid')
|
||||
.data(yScaleGrid.ticks(5))
|
||||
.attr(attr);
|
||||
|
||||
grid.exit().remove();
|
||||
|
||||
return grid.enter()
|
||||
.append('line')
|
||||
.attr(attr);
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,242 @@
|
||||
import Ember from 'ember';
|
||||
import moment from 'moment';
|
||||
|
||||
const {
|
||||
$,
|
||||
get,
|
||||
set,
|
||||
run,
|
||||
observer,
|
||||
Component,
|
||||
A,
|
||||
} = Ember;
|
||||
|
||||
export default Component.extend({
|
||||
classNames: ['chart-hover-container'],
|
||||
svg: null,
|
||||
data: null,
|
||||
width: null,
|
||||
height: null,
|
||||
dateFormat: 'MMM D, YYYY',
|
||||
tooltipTopOffset: 11,
|
||||
tooltipLeftOffset: 5,
|
||||
|
||||
initObserver: observer('svg', 'height', 'width', function() {
|
||||
this._super(...arguments);
|
||||
|
||||
const svg = get(this, 'svg');
|
||||
const height = get(this, 'height');
|
||||
|
||||
if(!svg || !height) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.renderContainer(svg);
|
||||
}),
|
||||
|
||||
widthObserver: observer('width', function() {
|
||||
const width = get(this, 'width');
|
||||
const backgroundMargin = get(this, 'backgroundMargin');
|
||||
const hoverArea = get(this, 'hoverArea');
|
||||
|
||||
hoverArea
|
||||
.attr("width", width)
|
||||
.attr("transform", "translate(" + backgroundMargin.left + "," + backgroundMargin.top + ")");
|
||||
}),
|
||||
|
||||
didInsertElement() {
|
||||
this._super(...arguments);
|
||||
|
||||
$('body').on('click touchstart', this.hideAll.bind(this));
|
||||
},
|
||||
|
||||
willDestroyElement() {
|
||||
this._super(...arguments);
|
||||
|
||||
$('body').off('click touchstart', this.hideAll.bind(this));
|
||||
},
|
||||
|
||||
hideAll(event) {
|
||||
if($(event.target).closest('.flaredown-svg-group').length === 0){
|
||||
get(this, 'line').style("display", "none");
|
||||
get(this, 'tooltipArea').css('display', 'none');
|
||||
}
|
||||
},
|
||||
|
||||
renderContainer(svg){
|
||||
if(!svg.select('.hover-area').empty()){
|
||||
return;
|
||||
}
|
||||
|
||||
const svgHight = get(this, 'height');
|
||||
|
||||
const line = svg.append('line')
|
||||
.attr('class', 'hover-line')
|
||||
.attr('style', 'display:none;')
|
||||
.attr('y1', 0)
|
||||
.attr('y2', svgHight)
|
||||
.attr('transform', 'translate(0, -10)');
|
||||
|
||||
set(this, 'line', line);
|
||||
|
||||
const tooltipArea = this.$('.tooltip-area');
|
||||
set(this, 'tooltipArea', tooltipArea);
|
||||
|
||||
const hoverArea = svg.append("rect")
|
||||
.attr("class", "hover-area")
|
||||
.attr("width", get(this, 'width'))
|
||||
.attr("height", svgHight)
|
||||
.style("fill", "none")
|
||||
.style("pointer-events", "all")
|
||||
.on("mouseover", function() {
|
||||
line.style("display", 'block');
|
||||
tooltipArea.css('display', 'block');
|
||||
})
|
||||
.on("mouseout", function() {
|
||||
if ($(event.toElement).closest('.tooltip-area').length === 0) {
|
||||
line.style("display", "none");
|
||||
tooltipArea.css("display", "none");
|
||||
}
|
||||
})
|
||||
.on("mousemove", () => {
|
||||
run(this, this.mouseMove, event);
|
||||
})
|
||||
.on("touchmove", () => {
|
||||
run(this, this.mouseMove, event);
|
||||
})
|
||||
.on("touchstart", () => {
|
||||
line.style("display", 'block');
|
||||
tooltipArea.css("display", "block");
|
||||
});
|
||||
|
||||
set(this, 'hoverArea', hoverArea);
|
||||
},
|
||||
|
||||
mouseMove(e) {
|
||||
if (e instanceof $.Event) {
|
||||
return;
|
||||
}
|
||||
|
||||
const xScale = get(this, 'xScale');
|
||||
const mouseX = e.offsetX || e.changedTouches[0].screenX; //d3.mouse(hoverArea.node())[0];
|
||||
const xValue = moment(xScale.invert(mouseX));
|
||||
|
||||
if(this.isOutOfRangeDates(xValue)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(xValue.hours() >= 12){
|
||||
xValue.add(1, 'days').startOf('day');
|
||||
} else {
|
||||
xValue.startOf('day');
|
||||
}
|
||||
|
||||
const x = xScale(xValue.toDate().getTime());
|
||||
|
||||
get(this, 'line')
|
||||
.attr('x1', x)
|
||||
.attr('x2', x);
|
||||
|
||||
this.showTooltip(xValue, x);
|
||||
e.stopPropagation();
|
||||
},
|
||||
|
||||
isOutOfRangeDates(date) {
|
||||
const startAt = get(this, 'startAt').startOf('day');
|
||||
const endAt = get(this, 'endAt');
|
||||
|
||||
return date < startAt || date > endAt;
|
||||
},
|
||||
|
||||
showTooltip(xValue, x) {
|
||||
const xValueFormatted = xValue.format('YYYY-MM-DD');
|
||||
const tooltipData = this.tooltipData(xValueFormatted);
|
||||
if(tooltipData.empty) {
|
||||
return;
|
||||
}
|
||||
|
||||
const lineItemList = tooltipData
|
||||
.filter((item) => (!item.marker && $.isNumeric(item.y) || (item.category == 'treatments' && item.is_taken)))
|
||||
.map((item) => {
|
||||
let value = this.tooltipItemValue(item);
|
||||
|
||||
return `<div class="line-item">
|
||||
<span class="colorable-clr-${item.color_id}">${item.label}</span>
|
||||
<span>${value}</span>
|
||||
</div>`
|
||||
}).join(' ');
|
||||
|
||||
let markerItemList = tooltipData.filter((item) => item.marker && item.category !== 'treatments' ).map((item) => {
|
||||
return `<span class="marker-item colorable-clr-${item.color_id}">${item.label}, </span>`
|
||||
}).join(' ');
|
||||
|
||||
const tooltipArea = get(this, 'tooltipArea');
|
||||
const hoverCenter = get(this, 'width')/2;
|
||||
const tooltipLeftOffset = get(this, 'tooltipLeftOffset');
|
||||
|
||||
let tooltipLeft = x <= hoverCenter ? (x + tooltipLeftOffset) : (x - tooltipLeftOffset - tooltipArea.width() - get(this, 'backgroundMargin.left'));
|
||||
|
||||
const isAuthor = get(this, 'session.email') === get(this, 'data.author_email');
|
||||
|
||||
const headerBody = (get(this, 'session.isAuthenticated') && isAuthor) ?
|
||||
`<a href=/checkin?date=${xValueFormatted}>
|
||||
<b>${xValue.format(get(this, 'dateFormat'))}</b>
|
||||
<img src="/assets/nav_icons/arrow-right.svg">
|
||||
</a>` :
|
||||
`<b>${xValue.format(get(this, 'dateFormat'))}</b>`;
|
||||
|
||||
const tooltipHeader = `<div class="tooltip-header">${headerBody}</div>`
|
||||
|
||||
tooltipArea
|
||||
.css('visibility', 'visible')
|
||||
.html(() => {
|
||||
return `${tooltipHeader}` + `<div class="tooltip-items">
|
||||
${lineItemList}
|
||||
<div class="marker-items">${markerItemList}</div>
|
||||
</div>`;
|
||||
})
|
||||
.css('top', get(this, 'tooltipTopOffset'))
|
||||
.css('left', tooltipLeft);
|
||||
},
|
||||
|
||||
tooltipData(xValueFormatted) {
|
||||
let filteredItems = A();
|
||||
|
||||
get(this, 'data.series').map((item) => {
|
||||
let filteredByDate = item.data.filterBy('x', xValueFormatted);
|
||||
if(filteredByDate.length == 0) {
|
||||
return;
|
||||
} else {
|
||||
let element = filteredByDate[0];
|
||||
element.label = item.label;
|
||||
element.color_id = item.color_id;
|
||||
element.marker = item.type == 'marker';
|
||||
element.static = item.subtype == 'static';
|
||||
element.category = item.category;
|
||||
|
||||
return filteredItems.pushObjects(filteredByDate);
|
||||
}
|
||||
});
|
||||
|
||||
return filteredItems.filter((item) => !item.average);
|
||||
},
|
||||
|
||||
tooltipItemValue(item) {
|
||||
if(item.static) {
|
||||
if(item.marker) {
|
||||
return item.category == 'treatments' ? `${item.y || '​'}` : '';
|
||||
} else {
|
||||
let y = item.y % 1 === 0 ? item.y : item.y.toFixed(1);
|
||||
return `${y}/4`;
|
||||
}
|
||||
} else {
|
||||
let units = "";
|
||||
|
||||
if(item.category == "weathersMeasures") {
|
||||
units = item.label == "Avg daily humidity" ? '%' : 'mb';
|
||||
}
|
||||
|
||||
return `${item.y}` + `${units}`;
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,199 @@
|
||||
import Ember from 'ember';
|
||||
import moment from 'moment';
|
||||
import * as d3 from "d3";
|
||||
|
||||
const {
|
||||
$,
|
||||
get,
|
||||
set,
|
||||
computed,
|
||||
observer,
|
||||
Component,
|
||||
run: { scheduleOnce }
|
||||
} = Ember;
|
||||
|
||||
export default Component.extend({
|
||||
data: null,
|
||||
chart: null,
|
||||
index: 1,
|
||||
isRendered: false,
|
||||
svgInitial: false,
|
||||
|
||||
renderObserver: observer('chart.svg', 'chart.width', 'chart.height', 'isRendered', 'onNavigate', function() {
|
||||
if(get(this, 'chart.svg') && get(this, 'isRendered')) {
|
||||
scheduleOnce('afterRender', this, this.renderChart);
|
||||
}
|
||||
}),
|
||||
|
||||
isCurrentEndDate: computed('chart.endAt', function() {
|
||||
return get(this, 'chart.endAt').startOf('day').toDate().getTime() == moment().startOf('day').toDate().getTime();
|
||||
}),
|
||||
|
||||
didInsertElement() {
|
||||
this._super(...arguments);
|
||||
|
||||
set(this, 'isRendered', true);
|
||||
set(this, 'yScaleDynamic', d3.scale.linear().range([get(this, 'chart.svgLineAreaHeight'), 0]));
|
||||
},
|
||||
|
||||
renderChart() {
|
||||
const data = get(this, 'data');
|
||||
let chartData = data.data
|
||||
|
||||
if(chartData) {
|
||||
switch(data.type) {
|
||||
case 'marker': {
|
||||
if (data.category == 'treatments') {
|
||||
chartData = data.data.filter((item) => item.is_taken)
|
||||
}
|
||||
|
||||
this.renderMarker(chartData, data.index, data.label);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
this.renderLine(chartData.filter((i) => $.isNumeric(i.y)), data.subtype, data.index, data.label);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// TODO: render "No Data"
|
||||
// console.log('No data!');
|
||||
}
|
||||
},
|
||||
|
||||
renderMarker(data, index, label) {
|
||||
const svg = get(this, 'chart.svg');
|
||||
const dotsAreas = svg.select('g.dots-area');
|
||||
|
||||
const svgInitial = get(this, 'chart.svgInitial');
|
||||
const backgroundMarginRight = get(this, 'chart.backgroundMarginRight');
|
||||
|
||||
const width = svgInitial ? get(this, 'chart.width') - 2*backgroundMarginRight : get(this, 'chart.width');
|
||||
const xScale = get(this, 'chart.xScale');
|
||||
const colorId = get(this, 'data.color_id');
|
||||
|
||||
const y = get(this, 'chart.svgLineAreaHeight') + get(this, 'chart.svgLineOffset')*(index + 1) + get(this, 'chart.svgLineHeight')*index;
|
||||
|
||||
const lineWidth = get(this, 'isCurrentEndDate') ? get(this, 'chart.svgChartWidth') : width;
|
||||
|
||||
const lineData = [{x: 0, y: y, x2: width, y2: y}];
|
||||
const paths = dotsAreas
|
||||
.selectAll(`.dot-path-${index}`) // for adding new pathes
|
||||
.data(lineData)
|
||||
.attr('x1', 0)
|
||||
.attr('y1', y)
|
||||
.attr('x2', lineWidth)
|
||||
.attr('y2', y);
|
||||
|
||||
paths.exit().remove();
|
||||
paths.enter()
|
||||
.append('line')
|
||||
.attr('class', `line colorable-stroke-${colorId} dot-path-${index}`)
|
||||
.attr('x1', 0)
|
||||
.attr('y1', y)
|
||||
.attr('x2', lineWidth)
|
||||
.attr('y2', y)
|
||||
.attr('stroke', `${colorId}`)
|
||||
.style('stroke-dasharray', ('3, 3'));
|
||||
|
||||
const dots = dotsAreas
|
||||
.selectAll(`.dot-${index}`)
|
||||
.data(data)
|
||||
.attr('cx', (d) => xScale(moment(d.x).toDate().getTime()))
|
||||
.attr('cy', () => y);
|
||||
|
||||
dots.enter()
|
||||
.append('circle')
|
||||
.attr('class', `colorable-fill-${colorId} dot-${index}`)
|
||||
.attr('r', 6)
|
||||
.attr('cx', (d) => {
|
||||
return xScale(moment(d.x).toDate().getTime());
|
||||
// return d.y == null ? null : xScale(moment(d.x).toDate().getTime());
|
||||
})
|
||||
.attr('cy', () => y)
|
||||
.attr('data-legend', () => {
|
||||
return label;
|
||||
});
|
||||
|
||||
this.renderLabel(dotsAreas, label, colorId, `translate(50, ${155 + 25*index})`);
|
||||
|
||||
dots.exit().remove();
|
||||
},
|
||||
|
||||
renderLine(data, subtype, index, label) {
|
||||
const svg = get(this, 'chart.svg');
|
||||
const lineAreas = svg.selectAll('g.lines-area');
|
||||
|
||||
const isStatic = subtype === 'static';
|
||||
const xScale = get(this, 'chart.xScale');
|
||||
let yScaleDynamic = get(this, 'yScaleDynamic');
|
||||
|
||||
if(!isStatic) {
|
||||
if(label == 'Avg daily humidity') {
|
||||
yScaleDynamic.domain([0, 100]);
|
||||
} else if(label == 'Harvey Bradshaw Index') {
|
||||
yScaleDynamic.domain([0, 25]);
|
||||
} else {
|
||||
yScaleDynamic.domain([
|
||||
d3.min(data, (d) => d.y ),
|
||||
d3.max(data, (d) => d.y )
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
const yScale = isStatic ? get(this, 'chart.yScaleStatic') : yScaleDynamic;
|
||||
const colorId = get(this, 'data.color_id');
|
||||
|
||||
const line = d3.svg.line()
|
||||
.x((d) => xScale(moment(d.x).toDate().getTime()))
|
||||
.y((d) => yScale(d.y));
|
||||
|
||||
const lines = lineAreas
|
||||
.selectAll(`.line-path-${index}`)
|
||||
.data(data)
|
||||
.attr('d', line(data));
|
||||
|
||||
lines.enter()
|
||||
.append('path')
|
||||
.attr('class', isStatic ? `line colorable-stroke-${colorId} line-path-${index}` : `line dynamic colorable-stroke-${colorId} line-path-${index}`)
|
||||
.attr('d', line(data))
|
||||
.attr('data-legend', () => {
|
||||
return label;
|
||||
});
|
||||
|
||||
this.renderLabel(lineAreas, label, colorId, "translate(265, 80)");
|
||||
|
||||
lines.exit().remove();
|
||||
|
||||
const lineDotsAreas = svg.selectAll('g.lines-dots-area');
|
||||
const filteredData = data.filter((d) => !d.average);
|
||||
|
||||
const dots = lineDotsAreas
|
||||
.selectAll(`.dot-line-${index}`)
|
||||
.data(filteredData)
|
||||
.attr('cx', (d) => xScale(moment(d.x).toDate().getTime()) )
|
||||
.attr('cy', (d) => yScale(moment(d.y).toDate().getTime()) );
|
||||
|
||||
dots.enter()
|
||||
.append('circle')
|
||||
.attr('class', `colorable-stroke-${colorId} dot-line-${index}`)
|
||||
.attr('r', 4)
|
||||
.attr('stroke-width', 2)
|
||||
.attr('fill', 'white')
|
||||
.attr('cx', (d) => xScale(moment(d.x).toDate().getTime()) )
|
||||
.attr('cy', (d) => yScale(moment(d.y).toDate().getTime()) );
|
||||
|
||||
dots.exit().remove();
|
||||
},
|
||||
|
||||
renderLabel(selection, label, colorId, translate) {
|
||||
if(get(this, 'initialSvg')) {
|
||||
selection.append('text')
|
||||
.attr('x', get(this, 'width'))
|
||||
.attr("dy", ".35em")
|
||||
.text(label)
|
||||
.attr('transform', translate)
|
||||
.attr('class', `colorable-fill-${colorId}`);
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,21 @@
|
||||
import Ember from 'ember';
|
||||
|
||||
const {
|
||||
get,
|
||||
computed,
|
||||
Component,
|
||||
} = Ember;
|
||||
|
||||
export default Component.extend({
|
||||
tagName: 'span',
|
||||
classNames: ['trackableItem'],
|
||||
classNameBindings: ['isLine:line:circle', 'colorableClass'],
|
||||
|
||||
isLine: computed('item.type', function() {
|
||||
return get(this, 'item.type') == 'line';
|
||||
}),
|
||||
|
||||
colorableClass: computed('item.color_id', function() {
|
||||
return `colorable-clr-${get(this, 'item.color_id')}`;
|
||||
}),
|
||||
});
|
||||
@@ -0,0 +1,160 @@
|
||||
import Ember from 'ember';
|
||||
import ChartDataRetrieve from 'flaredown/mixins/chart/chart-data-retrieve';
|
||||
import { translationMacro as t } from "ember-i18n";
|
||||
|
||||
const {
|
||||
get,
|
||||
set,
|
||||
computed,
|
||||
computed: {
|
||||
not
|
||||
},
|
||||
inject: { service },
|
||||
observer,
|
||||
setProperties,
|
||||
Component,
|
||||
A,
|
||||
} = Ember;
|
||||
|
||||
export default Component.extend(ChartDataRetrieve, {
|
||||
i18n: service(),
|
||||
store: service(),
|
||||
|
||||
model: null,
|
||||
maxTrackables: 10,
|
||||
showMessage: false,
|
||||
|
||||
classNames: ['flaredown-white-box'],
|
||||
selectLabel: t("history.step.creation.selectLabel"),
|
||||
deleteText: t("history.step.creation.deleteText"),
|
||||
saveText: t("history.step.creation.buttonText"),
|
||||
maxTrackablesText: t("history.step.creation.maxTrackablesText"),
|
||||
|
||||
disableSaveBtn: not('model.includes.length'),
|
||||
|
||||
chartEnablerPlaceholder: computed('isChartEnablerDisabled', function() {
|
||||
return get(this, 'isChartEnablerDisabled') ? 'No items to add' : 'Add symptoms, treatments and more...';
|
||||
}),
|
||||
|
||||
init() {
|
||||
this._super(...arguments);
|
||||
|
||||
get(this, 'store')
|
||||
.queryRecord('chart-list', {})
|
||||
.then((res) => {
|
||||
set(this, 'payload', get(res, 'payload'));
|
||||
});
|
||||
},
|
||||
|
||||
didReceiveAttrs() {
|
||||
this._super(...arguments);
|
||||
|
||||
const model = get(this, 'model');
|
||||
const store = get(this, 'store');
|
||||
|
||||
if(model === null && store) {
|
||||
set(this, 'model', store.createRecord('pattern'));
|
||||
}
|
||||
},
|
||||
|
||||
maxTrackablesObserver: observer('model.includes.length', function() {
|
||||
let length = get(this, 'model.includes.length');
|
||||
const maxTrackables = get(this, 'maxTrackables');
|
||||
|
||||
if(length >= maxTrackables) {
|
||||
setProperties(this, { showMessage: true, disableSaveBtn: true });
|
||||
} else {
|
||||
setProperties(this, { showMessage: false, disableSaveBtn: false });
|
||||
}
|
||||
}),
|
||||
|
||||
patternIncludesObserver: observer('payload', function() {
|
||||
const patternIncludes = A([]);
|
||||
const payload = get(this, 'payload');
|
||||
|
||||
Object
|
||||
.keys(payload)
|
||||
.forEach(category => {
|
||||
const categoryCharts = payload[category];
|
||||
|
||||
categoryCharts.forEach(chart => {
|
||||
let chart_id = chart[0];
|
||||
|
||||
if(category === 'weathersMeasures') {
|
||||
chart_id = chart_id == 1 ? 'humidity' : 'pressure';
|
||||
}
|
||||
|
||||
patternIncludes.pushObject({
|
||||
id: chart_id,
|
||||
category,
|
||||
label: chart[1],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
set(this, 'patternIncludes', patternIncludes);
|
||||
}),
|
||||
|
||||
patternOptionsObserver: observer('patternIncludes', 'model.includes', function() {
|
||||
const includes = get(this, 'model.includes');
|
||||
const patternIncludes = get(this, 'patternIncludes');
|
||||
|
||||
set(this, 'options', A(patternIncludes.filter((pattern) => {
|
||||
return includes.filter((inc) => {
|
||||
return inc.id == pattern.id && inc.category == pattern.category;
|
||||
}).length === 0;
|
||||
})));
|
||||
}),
|
||||
|
||||
actions: {
|
||||
handleChange(obj) {
|
||||
const maxTrackables = get(this, 'maxTrackables');
|
||||
const includes = get(this, 'model.includes');
|
||||
const options = get(this, 'options');
|
||||
|
||||
const exist = includes.find((i) => {
|
||||
return get(i, 'id') == get(obj, 'id') && get(i, 'category') == get(obj, 'category');
|
||||
});
|
||||
|
||||
options.removeObject(obj);
|
||||
|
||||
if(exist == null && includes.length < maxTrackables) {
|
||||
includes.pushObject(obj);
|
||||
} else {
|
||||
set(this, 'showMessage', true);
|
||||
}
|
||||
},
|
||||
|
||||
clicked(obj) {
|
||||
get(this, 'model.includes').removeObject(obj);
|
||||
|
||||
let options = get(this, 'options');
|
||||
|
||||
const exist = options.find((i) => {
|
||||
return get(i, 'id') == get(obj, 'id') && get(i, 'category') == get(obj, 'category');
|
||||
});
|
||||
|
||||
if(exist == null && obj.category == 'weathersMeasures') {
|
||||
options.pushObject(obj);
|
||||
}
|
||||
},
|
||||
|
||||
savePattern() {
|
||||
get(this, 'model').save().then(() => {
|
||||
this.sendAction('onSaved');
|
||||
});
|
||||
},
|
||||
|
||||
deletePattern() {
|
||||
get(this, 'model').destroyRecord().then(() => {
|
||||
this.sendAction('onSaved');
|
||||
});
|
||||
},
|
||||
|
||||
cancel() {
|
||||
get(this, 'model').deleteRecord();
|
||||
|
||||
this.sendAction('onCanceled');
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,151 @@
|
||||
import Ember from 'ember';
|
||||
import DS from 'ember-data';
|
||||
import moment from 'moment';
|
||||
|
||||
const {
|
||||
get,
|
||||
set,
|
||||
computed,
|
||||
computed: { alias },
|
||||
Component,
|
||||
inject: {
|
||||
service,
|
||||
},
|
||||
setProperties,
|
||||
A,
|
||||
} = Ember;
|
||||
|
||||
export default Component.extend({
|
||||
ajax: service('ajax'),
|
||||
patternsLoading: service('patterns-loading'),
|
||||
logoVisiability: service(),
|
||||
|
||||
page: 1,
|
||||
loadingPatterns: alias('patternsLoading.loadingPatterns'),
|
||||
patterns: [],
|
||||
colorIds: A(),
|
||||
backgroundMargin: { right: 10, left: 10 , top: 0 },
|
||||
|
||||
startAt: moment().subtract(14, 'days'), // 7 daysRadius * 2
|
||||
endAt: moment(),
|
||||
|
||||
chartData: computed('patterns.@each.id', 'startAt', 'endAt', 'daysRangeOffset', function() {
|
||||
const patterns = get(this, 'patterns');
|
||||
|
||||
if(!patterns) {
|
||||
return;
|
||||
}
|
||||
|
||||
const ids = patterns.mapBy('id');
|
||||
const chartDataPromise = get(this, 'chartDataPromise');
|
||||
const promise = get(this, 'ajax').request('/charts_pattern', {
|
||||
data: {
|
||||
pattern_ids: ids,
|
||||
start_at: get(this, 'startAt').format('YYYY-MM-DD'),
|
||||
end_at: get(this, 'endAt').format('YYYY-MM-DD'),
|
||||
offset: get(this, 'daysRangeOffset') || 1,
|
||||
}
|
||||
}).then((data) => data.charts_pattern);
|
||||
|
||||
set(chartDataPromise, 'promise', promise);
|
||||
|
||||
return chartDataPromise;
|
||||
}),
|
||||
|
||||
init() {
|
||||
this._super(...arguments);
|
||||
|
||||
set(this, 'chartDataPromise', DS.PromiseArray.create({}));
|
||||
},
|
||||
|
||||
didInsertElement() {
|
||||
this._super(...arguments);
|
||||
|
||||
// needs to be loaded
|
||||
get(this, 'chartData');
|
||||
|
||||
set(this, 'logoVisiability.showHeaderPath', false);
|
||||
|
||||
const resize = () => {
|
||||
const selection = this.$();
|
||||
if (!selection) {
|
||||
return;
|
||||
}
|
||||
const width = selection.width();
|
||||
|
||||
set(this, 'indexPageWidth', width);
|
||||
};
|
||||
|
||||
window.addEventListener("resize", resize);
|
||||
resize();
|
||||
},
|
||||
|
||||
willDestroyElement(){
|
||||
set(this, '_isDestroyed', true);
|
||||
},
|
||||
|
||||
authorName: computed('patterns.@each.authorName', function() {
|
||||
const names = get(this, 'patterns').mapBy('authorName').compact();
|
||||
const notEmpty = names.length > 0 ? names.filter((i) => i.length > 0).length > 0 : null;
|
||||
|
||||
return notEmpty ? get(names, 'firstObject') : 'User';
|
||||
}),
|
||||
|
||||
daysRangeOffset: computed('indexPageWidth', 'daysRange', function() {
|
||||
const backgroundMargin = get(this, 'backgroundMargin');
|
||||
const backgroundWidth = get(this, 'indexPageWidth') - backgroundMargin.left - backgroundMargin.right;
|
||||
const daysRange = get(this, 'daysRange');
|
||||
|
||||
return Math.ceil((daysRange*backgroundMargin.left)/backgroundWidth);
|
||||
}),
|
||||
|
||||
daysRange: computed('startAt', 'endAt', function() {
|
||||
return moment.duration(get(this, 'endAt') - get(this, 'startAt')).asDays();
|
||||
}),
|
||||
|
||||
fixPatternColors(chartData) {
|
||||
chartData.map((chart) => {
|
||||
return this.addChartAttributes(chart.series);
|
||||
});
|
||||
},
|
||||
|
||||
addChartAttributes(series) {
|
||||
let index = 0;
|
||||
|
||||
series.map((i) => { // setColorId()
|
||||
i.index = index;
|
||||
index += 1;
|
||||
})
|
||||
},
|
||||
|
||||
setColorId() {
|
||||
return get(this, 'colorIds').shiftObject();
|
||||
},
|
||||
|
||||
actions: {
|
||||
newPattern() {
|
||||
this.sendAction('onCreate');
|
||||
},
|
||||
|
||||
edit(pattern){
|
||||
this.sendAction('onEdit', pattern);
|
||||
},
|
||||
|
||||
crossedTheLine() {
|
||||
set(this, 'loadingPatterns', true);
|
||||
|
||||
this.sendAction('onRequest', this.incrementProperty('page', 1));
|
||||
},
|
||||
|
||||
navigate(days) {
|
||||
const startAt = moment(get(this, 'startAt')).add(days, 'days');
|
||||
const endAt = moment(get(this, 'endAt')).add(days, 'days');
|
||||
|
||||
setProperties(this, { startAt: startAt, endAt: endAt });
|
||||
},
|
||||
|
||||
sharePattern() {
|
||||
this.transitionTo('patterns');
|
||||
},
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,14 @@
|
||||
import Ember from 'ember';
|
||||
import { translationMacro as t } from "ember-i18n";
|
||||
|
||||
const {
|
||||
inject: { service },
|
||||
Component
|
||||
} = Ember;
|
||||
|
||||
export default Component.extend({
|
||||
i18n: service(),
|
||||
|
||||
classNames: ['flaredown-transparent-box'],
|
||||
buttonLabel: t("history.step.initial.buttonText"),
|
||||
});
|
||||
@@ -0,0 +1,72 @@
|
||||
import Ember from 'ember';
|
||||
import DatesRetriever from 'flaredown/mixins/chart/dates-retriever';
|
||||
import moment from 'moment';
|
||||
|
||||
const {
|
||||
get,
|
||||
computed,
|
||||
Component,
|
||||
} = Ember;
|
||||
|
||||
export default Component.extend(DatesRetriever, {
|
||||
yLine: [1, 1, 3, 3, 3, 1, 1, 0, 0, 0],
|
||||
yMarkerIndexes: { 'B12': [3, 4, 5, 6], 'Alcohol': [2, 3] },
|
||||
|
||||
dates: computed('yLine', function() {
|
||||
const daysCount = (get(this, 'yLine.length'));
|
||||
let res = [];
|
||||
|
||||
for(let i = 0; i < daysCount; i++) {
|
||||
res.push(moment().add(i+1, 'days').format('YYYY-MM-DD'));
|
||||
}
|
||||
|
||||
return res.sort();
|
||||
}),
|
||||
|
||||
lineData: computed('yLine', 'dates', function() {
|
||||
const dates = get(this, 'dates');
|
||||
const yLine = get(this, 'yLine');
|
||||
|
||||
let datesHash = dates.map((date) => {
|
||||
return { x: date, y: yLine[dates.indexOf(date)] };
|
||||
})
|
||||
|
||||
get(datesHash, 'firstObject').average = true;
|
||||
get(datesHash, 'lastObject').average = true;
|
||||
|
||||
return datesHash;
|
||||
}),
|
||||
|
||||
markerFirstData(key) {
|
||||
const dates = get(this, 'dates');
|
||||
const yMarkerIndexes = get(this, 'yMarkerIndexes');
|
||||
|
||||
let yValues = yMarkerIndexes[key];
|
||||
|
||||
let filteredDates = dates.filter((date) => {
|
||||
if(yValues.includes(dates.indexOf(date))) {
|
||||
return date;
|
||||
}
|
||||
});
|
||||
|
||||
return filteredDates.map((i) => {
|
||||
return {x: i}
|
||||
})
|
||||
},
|
||||
|
||||
data: computed('dataLine', function() {
|
||||
return { pattern_name: 'initial', series: [
|
||||
{ type: 'line', subtype: 'static', label: 'Fatigue', category: 'conditions', color_id: 1, data: get(this, 'lineData') },
|
||||
{ type: 'marker', subtype: 'static', label: 'B12 supplement', category: 'treatments', color_id: 2, data: this.markerFirstData('B12') },
|
||||
{ type: 'marker', subtype: 'static', label: 'Alcohol', category: 'treatments', color_id: 3, data: this.markerFirstData('Alcohol') }
|
||||
]}
|
||||
}),
|
||||
|
||||
startAt: computed('dates', function() {
|
||||
return moment(get(this, 'dates.firstObject'))
|
||||
}),
|
||||
|
||||
endAt: computed('dates', function() {
|
||||
return moment(get(this, 'dates.lastObject')).subtract(1, 'days');
|
||||
}),
|
||||
});
|
||||
Reference in New Issue
Block a user