d3-stack-time
Version:
701 lines (678 loc) • 20.9 kB
JavaScript
var d3 = require('d3');
var tip = require('d3-tip');
d3.tip = tip;
var legend = require('d3-svg-legend');
exports = module.exports = function () {
'use strict';
// Public variables with default settings
var backgroundColor = '#f1f1f1';
var padding = {
top: 40,
rigth: 100,
bottom: 30,
left: 80
};
var data;
var stackColor = [
'hsla(0,0%,0%,.1)',
'#33ff33',
'hsla(0,0%,0%,.1)',
'red',
'#33ff33',
'yellow',
'hsla(0,0%,0%,.1)',
'red',
'#33ff33',
'yellow',
'#33ff33',
'hsla(0,0%,0%,.1)'
];
var stackPadding = 0.3;
var stackAlign = 0.1;
var titleText = 'title';
var titleTextX = 9;
var titleTextDY = '0.35em';
var titleTranslateX = 0;
var titleTranslateY = 0;
var titleColor = '#000';
var titleFontSize = '18';
var xAxisNum = 24;
var legendLineX1 = -6;
var legendLineX2 = 6;
var legendLineStroke = '#000';
var legendTextX = 9;
var legendTextDY = '0.35em';
var legendTextColor = '#000';
var lineColor = 'red';
var lineWidth = 1;
var stackLabelCustom = {};
var defaultYear = new Date().getFullYear();
var workMinute = 8 * 60; // 8工作时间
var elasticFirst = 8 * 60 + 30; // 弹性上班最早开始时间
var elasticLatest = 9 * 60 + 30; // 弹性上班最晚开始时间
var restMinute = 1.5 * 60; //午休时间
// Private variables
var ALL_MINUTE = 24 * 60; // 全天时间
var HALF_MINUTE = 12 * 60; // 半天时间
var AFTERNOON_START = HALF_MINUTE + restMinute; // 下午上班时间
var AFTERNOON_FIRST = workMinute + elasticFirst + restMinute; // 下午下班最早时间
var AFTERNOON_LATEST = workMinute + elasticLatest + restMinute; // 下午下班最晚时间
var STACK_LABEL = ['AMBefore', 'AMOver', 'AMRest', 'AMLate', 'AMWork', 'AMEarly', 'PMRest', 'PMLate', 'PMWork', 'PMEarly', 'PMOver', 'PMAfter'];
var STACK_LABEL_ZH = {
'AMBefore': '休息时间',
'AMOver': '加班时间',
'AMRest': '上午休息时间',
'AMLate': '上午迟到时间',
'AMWork': '上午工作时间',
'AMEarly': '上午早退时间',
'PMRest': '午休时间',
'PMLate': '下午迟到时间',
'PMWork': '下午工作时间',
'PMEarly': '下午早退时间',
'PMOver': '加班时间',
'PMAfter': '休息时间'
};
function chart(selection) {
selection.each(function () {
// 指定图表区域与尺寸
var svg = d3.select(this);
var width = +svg.attr('width') - padding.left - padding.rigth;
var height = +svg.attr('height') - padding.top - padding.bottom;
var chart = svg.append('g')
.attr('fill', backgroundColor)
.attr('transform', 'translate(' + padding.left + ',' + padding.top + ')')
.attr('viewbox', function () {
return (padding.left + ' ' + padding.top + ' ' + width + ' ' + height);
});
// x比例尺
var x = d3.scaleBand()
.rangeRound([0, width])
.padding(stackPadding)
.align(stackAlign);
// y比例尺
var y = d3.scaleLinear()
.rangeRound([height, 0]);
var yTime = d3.scaleTime()
.domain([new Date(defaultYear, 0, 1, 0, 0, 0), new Date(defaultYear, 0, 1, 23, 59, 59)])
.range([height, 0]);
// 堆积图颜色
var z = d3.scaleOrdinal()
.domain(STACK_LABEL)
// .range(d3.schemeCategory20);
.range(stackColor);
// 堆积图定义
var stack = d3.stack()
.offset(d3.stackOffsetExpand);
// 提示框
var tip = d3.tip()
.attr('class', 'd3-tip')
.html(function (d) {
var isWeekend = d.data['isWeekend'];
var t1 = d.data[STACK_LABEL[0]]; // 休息时间
var t2 = d.data[STACK_LABEL[1]]; // 上午加班时间
var t3 = d.data[STACK_LABEL[2]]; // 上午休息时间
var t4 = d.data[STACK_LABEL[3]]; // 上午迟到时间
var t5 = d.data[STACK_LABEL[4]]; // 上午工作时间
var t6 = d.data[STACK_LABEL[5]]; // 上午早退时间
var t7 = d.data[STACK_LABEL[6]]; // 下午休息时间
var t8 = d.data[STACK_LABEL[7]]; // 下午迟到时间
var t9 = d.data[STACK_LABEL[8]]; // 下午工作时间
var t10 = d.data[STACK_LABEL[9]]; // 下午早退时间
var t11 = d.data[STACK_LABEL[10]]; // 下午加班时间
var t12 = d.data[STACK_LABEL[11]]; // 休息时间
if (isWeekend && (t2 + t5 + t9 + t11) <= 0) {
return '<h3>' + d.data.xAxis + '<h3><p>周末休息</p>';
}
if (!isWeekend && (t2 + t5 + t9 + t11) <= 0) {
return '<h3>' + d.data.xAxis + '<h3><p>今日未打卡</p>';
}
var startTime = 0;
var endTime = 0;
var amOverTime = 0;
var pmOverTime = 0;
// 上午之间打卡
if (t2 + t5 > 0) {
startTime = HALF_MINUTE - t5 - t6;
} else {
startTime = HALF_MINUTE + t7 + t8;
}
endTime = HALF_MINUTE - t6 + t7 + t8 + t9;
amOverTime = t1;
pmOverTime = HALF_MINUTE + t7 + t9 + t11;
var amOverTimeStr = t2 > 0 ? (convert(amOverTime) + '--' + convert(startTime)) : 'none';
var pmOverTimeStr = t11 > 0 ? (convert(endTime) + '--' + convert(pmOverTime)) : 'none';
var str = '<h3>' + d.data.xAxis + '<h3>' +
'<p><span>工作时间:</span>' +
formatTime(t5 + t9) +
'<small>(' + convert(startTime) + '--' + convert(endTime) + ')</small></p>';;
if (t2) {
str += '<p><span>上午加班时间:</span>' +
formatTime(t2) +
'<small>(' + amOverTimeStr + ')</small></p>'
}
if (t11) {
str += '<p><span>下午加班时间:</span>' +
formatTime(t11) +
'<small>(' + pmOverTimeStr + ')</small></p>'
}
return str;
});
svg.call(tip);
// 画堆叠图
var newData = type(data);
x.domain(newData.map(function (d) {
return d.xAxis;
}));
var serie = chart.selectAll('.serie')
.data(stack.keys(STACK_LABEL)(newData))
.enter()
.append('g')
.attr('class', 'serie')
.attr('fill', function (d) {
return z(d.key);
});
serie.selectAll('rect')
.data(function (d) {
return d;
})
.enter()
.append('rect')
.attr('x', function (d) {
return x(d.data.xAxis);
})
.attr('y', function (d) {
return y(d[1]);
})
.attr('height', function (d) {
var h = y(d[0]) - y(d[1]);
return h > 0 ? h : 0;
})
.attr('width', x.bandwidth())
.on('mouseenter.tip', tip.show)
.on('mouseleave.tip', tip.hide);
// 画标题
var title = chart.append('g')
.attr('class', 'title')
.attr('transform', function (d) {
return 'translate(' + (width / 2 + titleTranslateX) + ',' + (-padding.top / 2 + titleTranslateY) + ')';
});
title.append('text')
.attr('x', titleTextX)
.attr('dy', titleTextDY)
.attr('fill', titleColor)
.attr('fontSize', titleFontSize)
.text(titleText);
// 画图例
// var legend = serie.append('g')
// .attr('class', 'legend')
// .attr('transform', function (d) {
// var d = d[d.length - 1];
// return 'translate(' + (x(d.data.xAxis) + x.bandwidth()) + ',' + ((y(d[0]) + y(d[1])) / 2) + ')';
// });
// legend.append('line')
// .attr('x1', legendLineX1)
// .attr('x2', legendLineX2)
// .attr('stroke', legendLineStroke);
// legend.append('text')
// .attr('x', legendTextX)
// .attr('dy', legendTextDY)
// .attr('fill', legendTextColor)
// .text(function (d) {
// return stackLabelCustom[d.key] ? stackLabelCustom[d.key] : STACK_LABEL_ZH[d.key];
// });
var ordinal = d3.scaleOrdinal()
.domain(['迟到', '工作', '早退', '休息'])
.range([stackColor[3], stackColor[4], stackColor[5], stackColor[0]]);
chart.append('g')
.attr('class', 'legendOrdinal')
.attr('transform', 'translate(0,-40)');
var legendOrdinal = legend.legendColor()
.shapeWidth(60)
.orient('horizontal')
.scale(ordinal);
chart.select('.legendOrdinal')
.call(legendOrdinal);
// 画坐标轴
chart.append('g')
.attr('class', 'axis axis--x')
.attr('transform', 'translate(0,' + height + ')')
.call(d3.axisBottom(x));
chart.append('g')
.attr('class', 'axis axis--y')
.call(d3.axisLeft(yTime).ticks(xAxisNum));
// 画线
drawLine(chart, y(elasticLatest / ALL_MINUTE), width);
drawLine(chart, y(AFTERNOON_FIRST / ALL_MINUTE), width);
});
/**
* 数据转换为堆叠图格式
*
* @param {any} data 原始数据
* @returns 堆叠图数据
*/
function type(data) {
var result = [];
for (var i = 0; i < data.values.length; i++) {
var day = new Date(defaultYear + '-' + data.axisX[i]).getDay();
var isWeekend = (day == 6 || day == 0) ? true : false;
// object
var o = {
xAxis: data.axisX[i],
isWeekend: isWeekend,
all: ALL_MINUTE
};
var originData = data.values[i];
var newData;
if (typeof originData == 'string') {
newData = originData.trim().split(' ');
} else {
newData = originData;
}
var len = newData.length;
if (len >= 2) {
var arr1 = newData[0].split(':');
var arr2 = newData[len - 1].split(':');
var t1 = parseInt(arr1[0]) * 60 + parseInt(arr1[1]); // 上班打卡时间
var t2 = parseInt(arr2[0]) * 60 + parseInt(arr2[1]); // 下班打卡时间
var AMBefore = 0; // 休息时间
var AMOver = 0; // 预备时间,(超过阈值当作加班时间)
var AMRest = 0; // 上午休息时间
var AMLate = 0; // 上午迟到时间
var AMWork = 0; // 上午工作时间
var AMEarly = 0; // 上午早退时间
var PMRest = 0; // 午休时间
var PMLate = 0; // 下午迟到时间
var PMWork = 0; // 下午工作时间
var PMEarly = 0; // 下午早退时间
var PMOver = 0; // 加班时间,非正常情况下特殊加班用
var PMAfter = 0; // 休息时间
if (t2 <= elasticFirst) {
AMBefore = t1;
AMOver = t2 - t1;
AMRest = HALF_MINUTE - t2;
AMLate = 0;
AMWork = 0;
AMEarly = 0;
PMRest = 0;
PMLate = 0;
PMWork = 0;
PMEarly = 0;
PMOver = 0;
PMAfter = HALF_MINUTE;
} else if (t2 <= HALF_MINUTE) {
AMBefore = t1;
if (t1 < elasticFirst) {
AMOver = elasticFirst - t1;
} else {
AMOver = 0;
}
AMRest = 0;
if (t1 > elasticLatest) {
AMLate = t1 - elasticLatest;
} else {
AMLate = 0;
}
if (t1 <= elasticFirst) {
AMWork = t2 - elasticFirst;
} else if (t1 > elasticFirst) {
AMWork = t2 - t1;
} else {
AMWork = 0;
}
AMEarly = HALF_MINUTE - t2;
PMRest = 0;
PMLate = 0;
PMWork = 0
PMEarly = 0;
PMOver = 0;
PMAfter = HALF_MINUTE;
} else if (t2 <= AFTERNOON_START && t1 >= HALF_MINUTE) {
AMBefore = HALF_MINUTE;
AMOver = 0;
AMRest = 0;
AMLate = 0;
AMWork = 0;
AMEarly = 0;
PMRest = restMinute;
PMLate = 0;
PMWork = 0;
PMEarly = 0;
PMOver = 0;
PMAfter = HALF_MINUTE - restMinute;
} else if (t1 >= HALF_MINUTE) {
AMBefore = HALF_MINUTE;
AMOver = 0;
AMRest = 0;
AMLate = 0;
AMWork = 0;
AMEarly = 0;
PMRest = restMinute;
PMLate = t1 > AFTERNOON_START ? (t1 - AFTERNOON_START) : 0;
PMWork = (t2 - (t1 < AFTERNOON_START ? AFTERNOON_START : t1) >= workMinute) ? workMinute : (t2 - (t1 < AFTERNOON_START ? AFTERNOON_START : t1));
PMEarly = (t2 < AFTERNOON_FIRST) ? (AFTERNOON_FIRST - t2) : 0;
PMOver = (PMWork < workMinute) ? 0 : (t2 - (t1 < AFTERNOON_START ? AFTERNOON_START : t1) - workMinute);
PMAfter = ALL_MINUTE - (t2 < AFTERNOON_START ? AFTERNOON_START : t2) - PMEarly;
} else if (t1 >= AFTERNOON_LATEST) {
AMBefore = HALF_MINUTE;
AMOver = 0;
AMRest = 0;
AMLate = 0;
AMWork = 0;
AMEarly = 0;
PMRest = HALF_MINUTE - PMOver - PMAfter;
PMLate = 0;
PMWork = 0;
PMEarly = 0;
PMOver = t2 - t1;
PMAfter = ALL_MINUTE - t2;
} else {
AMOver = t1 > elasticFirst ? 0 : (elasticFirst - t1);
AMRest = 0;
AMLate = t1 < elasticLatest ? 0 : t1 - elasticLatest;
AMWork = HALF_MINUTE - (t1 >= elasticFirst ? t1 : elasticFirst);
AMEarly = 0;
AMBefore = t1 - AMLate;
PMRest = restMinute;
PMLate = 0;
if (t2 < AFTERNOON_START) {
PMWork = 0;
} else {
var temp = t2 - (t1 < elasticFirst ? elasticFirst : t1) - restMinute;
PMWork = temp > workMinute ? (workMinute - AMWork) : (temp - AMWork);
}
if (t2 < AFTERNOON_LATEST) {
PMEarly = workMinute - AMWork - PMWork;
} else {
PMEarly = 0;
}
PMOver = (t2 - (t1 < elasticFirst ? elasticFirst : t1) - restMinute - workMinute) > 0 ? (t2 - (t1 < elasticFirst ? elasticFirst : t1) - restMinute - workMinute) : 0;
PMAfter = ALL_MINUTE - (t2 < AFTERNOON_START ? AFTERNOON_START : t2) - PMEarly;
}
o[STACK_LABEL[0]] = AMBefore;
o[STACK_LABEL[1]] = AMOver;
o[STACK_LABEL[2]] = AMRest;
o[STACK_LABEL[3]] = AMLate;
o[STACK_LABEL[4]] = AMWork;
o[STACK_LABEL[5]] = AMEarly;
o[STACK_LABEL[6]] = PMRest;
o[STACK_LABEL[7]] = PMLate;
o[STACK_LABEL[8]] = PMWork;
o[STACK_LABEL[9]] = PMEarly;
o[STACK_LABEL[10]] = PMOver;
o[STACK_LABEL[11]] = PMAfter;
console.log((AMBefore + AMOver+ AMRest + AMLate + AMWork + AMEarly) == (PMAfter + PMOver + PMRest + PMLate + PMWork + PMEarly));
result.push(o);
} else {
o[STACK_LABEL[0]] = HALF_MINUTE;
o[STACK_LABEL[1]] = 0;
o[STACK_LABEL[2]] = 0;
o[STACK_LABEL[3]] = 0;
o[STACK_LABEL[4]] = 0;
o[STACK_LABEL[5]] = 0;
o[STACK_LABEL[6]] = 0;
o[STACK_LABEL[7]] = 0;
o[STACK_LABEL[8]] = 0;
o[STACK_LABEL[9]] = 0;
o[STACK_LABEL[10]] = 0;
o[STACK_LABEL[11]] = HALF_MINUTE;
result.push(o);
}
}
return result;
}
/**
* 分钟数转成时间,中文格式
*
* @param {any} time 分钟数
* @returns
*/
function formatTime(time) {
var hour;
var minute;
hour = Math.floor(time / 60);
if (time % 60 === 0) {
minute = 0;
} else {
minute = time % 60;
}
if (hour === 0) {
return minute + '分钟';
} else if (hour !== 0 && minute === 0) {
return hour + '小时';
} else {
return hour + '小时' + minute + '分钟';
}
}
/**
* 分钟数转成时间
*
* @param {any} time 分钟数
* @returns
*/
function convert(time) {
var hour;
var minute;
hour = Math.floor(time / 60);
if (time % 60 === 0) {
minute = 0;
} else {
minute = time % 60;
}
return hour + ':' + (minute < 10 ? ('0' + minute) : minute);
}
/**
* 画线
*
* @param {any} dom 操作区域
* @param {any} point 线的起点
* @param {any} width 线的长度
*/
function drawLine(dom, point, width) {
dom.append('line')
.attr('x1', 0)
.attr('y1', point)
.attr('x2', width)
.attr('y2', point)
.attr('stroke-width', lineWidth)
.attr('stroke', lineColor);
}
}
// 暴露对外方法
chart.backgroundColor = function (_) {
if (!arguments.length) {
return backgroundColor;
}
backgroundColor = _;
return chart;
};
chart.padding = function (_) {
if (!arguments.length) {
return padding;
}
padding = _;
return chart;
};
chart.data = function (_) {
if (!arguments.length) {
return data;
}
data = _;
return chart;
};
chart.stackColor = function (_) {
if (!arguments.length) {
return stackColor;
}
stackColor = _;
return chart;
};
chart.stackPadding = function (_) {
if (!arguments.length) {
return stackPadding;
}
stackPadding = _;
return chart;
};
chart.stackAlign = function (_) {
if (!arguments.length) {
return stackAlign;
}
stackAlign = _;
return chart;
};
chart.titleText = function (_) {
if (!arguments.length) {
return titleText;
}
titleText = _;
return chart;
};
chart.titleTextX = function (_) {
if (!arguments.length) {
return titleTextX;
}
titleTextX = _;
return chart;
};
chart.titleTextDY = function (_) {
if (!arguments.length) {
return titleTextDY;
}
titleTextDY = _;
return chart;
};
chart.titleTranslateX = function (_) {
if (!arguments.length) {
return titleTranslateX;
}
titleTranslateX = _;
return chart;
};
chart.titleTranslateY = function (_) {
if (!arguments.length) {
return titleTranslateY;
}
titleTranslateY = _;
return chart;
};
chart.titleColor = function (_) {
if (!arguments.length) {
return titleColor;
}
titleColor = _;
return chart;
};
chart.titleFontSize = function (_) {
if (!arguments.length) {
return titleFontSize;
}
titleFontSize = _;
return chart;
};
chart.xAxisNum = function (_) {
if (!arguments.length) {
return xAxisNum;
}
xAxisNum = _;
return chart;
};
chart.legendLineX1 = function (_) {
if (!arguments.length) {
return legendLineX1;
}
legendLineX1 = _;
return chart;
};
chart.legendLineX2 = function (_) {
if (!arguments.length) {
return legendLineX2;
}
legendLineX2 = _;
return chart;
};
chart.legendLineStroke = function (_) {
if (!arguments.length) {
return legendLineStroke;
}
legendLineStroke = _;
return chart;
};
chart.legendTextX = function (_) {
if (!arguments.length) {
return legendTextX;
}
legendTextX = _;
return chart;
};
chart.legendTextDY = function (_) {
if (!arguments.length) {
return legendTextDY;
}
legendTextDY = _;
return chart;
};
chart.legendTextColor = function (_) {
if (!arguments.length) {
return legendTextColor;
}
legendTextColor = _;
return chart;
};
chart.lineColor = function (_) {
if (!arguments.length) {
return lineColor;
}
lineColor = _;
return chart;
};
chart.lineWidth = function (_) {
if (!arguments.length) {
return lineWidth;
}
lineWidth = _;
return chart;
};
chart.stackLabelCustom = function (_) {
if (!arguments.length) {
return stackLabelCustom;
}
stackLabelCustom = _;
return chart;
};
chart.defaultYear = function (_) {
if (!arguments.length) {
return defaultYear;
}
defaultYear = _;
return chart;
};
chart.workMinute = function (_) {
if (!arguments.length) {
return workMinute;
}
workMinute = _;
return chart;
};
chart.elasticFirst = function (_) {
if (!arguments.length) {
return elasticFirst;
}
elasticFirst = _;
return chart;
};
chart.elasticLatest = function (_) {
if (!arguments.length) {
return elasticLatest;
}
elasticLatest = _;
return chart;
};
chart.restMinute = function (_) {
if (!arguments.length) {
return restMinute;
}
restMinute = _;
return chart;
};
return chart;
};