Merge pull request #36 from shwin0901/master

Add Searching Statistics page
This commit is contained in:
Folyd 2020-05-06 22:49:50 +08:00 committed by GitHub
commit ef6daebfe1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 397 additions and 0 deletions

View file

@ -0,0 +1,27 @@
text.month-name,
text.calendar-heatmap-legend-text,
text.day-initial {
font-size: 10px;
fill: inherit;
font-family: Helvetica, arial, "Open Sans", sans-serif;
}
rect.day-cell:hover {
stroke: #555555;
stroke-width: 1px;
}
.day-cell-tooltip {
position: absolute;
z-index: 9999;
padding: 5px 9px;
color: #bbbbbb;
font-size: 12px;
background: rgba(0, 0, 0, 0.85);
border-radius: 3px;
text-align: center;
}
.day-cell-tooltip > span {
font-family: Helvetica, arial, "Open Sans", sans-serif;
}
.calendar-heatmap {
box-sizing: initial;
}

View file

@ -0,0 +1,284 @@
function calendarHeatmap() {
// defaults
var width = 750;
var height = 160;
var baseX = 15;
var baseY = 20;
var legendWidth = 150;
var selector = 'body';
var SQUARE_LENGTH = 11;
var SQUARE_PADDING = 2;
var MONTH_LABEL_PADDING = 6;
var now = moment().endOf('day').toDate();
var yearAgo = moment().startOf('day').subtract(1, 'year').toDate();
var startDate = null;
var counterMap = {};
var data = [];
var max = null;
var colorRange = ['#D8E6E7', '#218380'];
var tooltipEnabled = true;
var tooltipUnit = 'contribution';
var legendEnabled = true;
var onClick = null;
var weekStart = 0; //0 for Sunday, 1 for Monday
var locale = {
months: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
days: ['S', 'M', 'T', 'W', 'T', 'F', 'S'],
No: 'No',
on: 'on',
Less: 'Less',
More: 'More'
};
var v = Number(d3.version.split('.')[0]);
// setters and getters
chart.data = function (value) {
if (!arguments.length) { return data; }
data = value;
counterMap = {};
data.forEach(function (element, index) {
var key = moment(element.date).format('YYYY-MM-DD');
var counter = counterMap[key] || 0;
counterMap[key] = counter + element.count;
});
return chart;
};
chart.max = function (value) {
if (!arguments.length) { return max; }
max = value;
return chart;
};
chart.selector = function (value) {
if (!arguments.length) { return selector; }
selector = value;
return chart;
};
chart.startDate = function (value) {
if (!arguments.length) { return startDate; }
yearAgo = value;
now = moment(value).endOf('day').add(1, 'year').toDate();
return chart;
};
chart.colorRange = function (value) {
if (!arguments.length) { return colorRange; }
colorRange = value;
return chart;
};
chart.tooltipEnabled = function (value) {
if (!arguments.length) { return tooltipEnabled; }
tooltipEnabled = value;
return chart;
};
chart.tooltipUnit = function (value) {
if (!arguments.length) { return tooltipUnit; }
tooltipUnit = value;
return chart;
};
chart.legendEnabled = function (value) {
if (!arguments.length) { return legendEnabled; }
legendEnabled = value;
return chart;
};
chart.onClick = function (value) {
if (!arguments.length) { return onClick(); }
onClick = value;
return chart;
};
chart.locale = function (value) {
if (!arguments.length) { return locale; }
locale = value;
return chart;
};
function chart() {
d3.select(chart.selector()).selectAll('svg.calendar-heatmap').remove(); // remove the existing chart, if it exists
var dateRange = ((d3.time && d3.time.days) || d3.timeDays)(yearAgo, now); // generates an array of date objects within the specified range
var monthRange = ((d3.time && d3.time.months) || d3.timeMonths)(moment(yearAgo).startOf('month').toDate(), now); // it ignores the first month if the 1st date is after the start of the month
var firstDate = moment(dateRange[0]);
if (chart.data().length == 0) {
max = 0;
} else if (max === null) {
max = d3.max(chart.data(), function (d) { return d.count; }); // max data value
}
// color range
var color = ((d3.scale && d3.scale.linear) || d3.scaleLinear)()
.range(chart.colorRange())
.domain([0, max]);
var tooltip;
var dayRects;
drawChart();
function drawChart() {
var svg = d3.select(chart.selector())
.style('position', 'relative')
.append('svg')
.attr('width', width)
.attr('class', 'calendar-heatmap')
.attr('height', height)
.style('padding', '36px');
dayRects = svg.selectAll('.day-cell')
.data(dateRange); // array of days for the last yr
var enterSelection = dayRects.enter().append('rect')
.attr('class', 'day-cell')
.attr('width', SQUARE_LENGTH)
.attr('height', SQUARE_LENGTH)
.attr('fill', function (d) { return color(countForDate(d)); })
.attr('x', function (d, i) {
var cellDate = moment(d);
var result = cellDate.week() - firstDate.week() + (firstDate.weeksInYear() * (cellDate.weekYear() - firstDate.weekYear()));
return baseX + result * (SQUARE_LENGTH + SQUARE_PADDING);
})
.attr('y', function (d, i) {
return baseY + MONTH_LABEL_PADDING + formatWeekday(d.getDay()) * (SQUARE_LENGTH + SQUARE_PADDING);
});
if (typeof onClick === 'function') {
(v === 3 ? enterSelection : enterSelection.merge(dayRects)).on('click', function (d) {
var count = countForDate(d);
onClick({ date: d, count: count });
});
}
if (chart.tooltipEnabled()) {
(v === 3 ? enterSelection : enterSelection.merge(dayRects)).on('mouseover', function (d, i) {
tooltip = d3.select(chart.selector())
.append('div')
.attr('class', 'day-cell-tooltip')
.html(tooltipHTMLForDate(d))
.style('left', function () { return baseX + Math.floor(i / 7) * SQUARE_LENGTH + 'px'; })
.style('top', function () {
return baseY + formatWeekday(d.getDay()) * (SQUARE_LENGTH + SQUARE_PADDING) + MONTH_LABEL_PADDING * 2 + 'px';
});
})
.on('mouseout', function (d, i) {
tooltip.remove();
});
}
if (chart.legendEnabled()) {
var colorRange = [color(0)];
for (var i = 3; i > 0; i--) {
colorRange.push(color(max / i));
}
var legendGroup = svg.append('g');
legendGroup.selectAll('.calendar-heatmap-legend')
.data(colorRange)
.enter()
.append('rect')
.attr('class', 'calendar-heatmap-legend')
.attr('width', SQUARE_LENGTH)
.attr('height', SQUARE_LENGTH)
.attr('x', function (d, i) { return (width - legendWidth) + (i + 1) * 13; })
.attr('y', height - 30 + SQUARE_PADDING)
.attr('fill', function (d) { return d; });
legendGroup.append('text')
.attr('class', 'calendar-heatmap-legend-text calendar-heatmap-legend-text-less')
.attr('x', width - legendWidth - 13)
.attr('y', height - 30 + SQUARE_LENGTH)
.text(locale.Less);
legendGroup.append('text')
.attr('class', 'calendar-heatmap-legend-text calendar-heatmap-legend-text-more')
.attr('x', (width - legendWidth + SQUARE_PADDING) + (colorRange.length + 1) * 13)
.attr('y', height - 30 + SQUARE_LENGTH)
.text(locale.More);
}
dayRects.exit().remove();
var monthLabels = svg.selectAll('.month')
.data(monthRange)
.enter().append('text')
.attr('class', 'month-name')
.text(function (d) {
return locale.months[d.getMonth()];
})
.attr('x', function (d, i) {
var matchIndex = 0;
dateRange.find(function (element, index) {
matchIndex = index;
return moment(d).isSame(element, 'month') && moment(d).isSame(element, 'year');
});
return baseX + Math.floor(matchIndex / 7) * (SQUARE_LENGTH + SQUARE_PADDING);
})
.attr('y', baseY + 0); // fix these to the top
locale.days.forEach(function (day, index) {
index = formatWeekday(index);
if (index % 2) {
svg.append('text')
.attr('class', 'day-initial')
.attr('transform', 'translate(5,' + (SQUARE_LENGTH + SQUARE_PADDING) * (index + 1) + ')')
.style('text-anchor', 'middle')
.attr('dy', `${baseY + 2}`)
.text(day);
}
});
}
function pluralizedTooltipUnit(count) {
if ('string' === typeof tooltipUnit) {
return (tooltipUnit + (count === 1 ? '' : 's'));
}
for (var i in tooltipUnit) {
var _rule = tooltipUnit[i];
var _min = _rule.min;
var _max = _rule.max || _rule.min;
_max = _max === 'Infinity' ? Infinity : _max;
if (count >= _min && count <= _max) {
return _rule.unit;
}
}
}
function tooltipHTMLForDate(d) {
var dateStr = moment(d).format('ddd, MMM Do YYYY');
var count = countForDate(d);
return '<span><strong>' + (count ? count : locale.No) + ' ' + pluralizedTooltipUnit(count) + '</strong> ' + locale.on + ' ' + dateStr + '</span>';
}
function countForDate(d) {
var key = moment(d).format('YYYY-MM-DD');
return counterMap[key] || 0;
}
function formatWeekday(weekDay) {
if (weekStart === 1) {
if (weekDay === 0) {
return 6;
} else {
return weekDay - 1;
}
}
return weekDay;
}
var daysOfChart = chart.data().map(function (day) {
return day.date.toDateString();
});
}
return chart;
}

2
extension/libs/d3.min.js vendored Normal file

File diff suppressed because one or more lines are too long

2
extension/libs/moment.min.js vendored Normal file

File diff suppressed because one or more lines are too long

21
extension/stats/index.css Normal file
View file

@ -0,0 +1,21 @@
* {
margin: 0;
padding: 0;
}
.logo {
padding: 40px;
font-family: "Alfa Slab One", serif;
font-size: 32px;
text-align: center;
display: flex;
}
.logo img {
width: 60px;
margin: 0 25px;
}
.flex-layout {
display: flex;
justify-content: center;
align-items: center;
}

View file

@ -0,0 +1,33 @@
<!DOCTYPE html>
<html lang="en" style="background-color: #fcfaf6;">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Rust Search Extension Statistics</title>
<script type="text/javascript" src="../libs/calendar-heatmap.js"></script>
<script type="text/javascript" src="../libs/d3.min.js"></script>
<script type="text/javascript" src="../libs/moment.min.js"></script>
<link rel="stylesheet" href="../libs/calendar-heatmap.css">
<link rel="stylesheet" href="index.css">
<link href="https://fonts.googleapis.com/css?family=Alfa+Slab+One&display=swap" rel="stylesheet">
</head>
<body>
<div class="flex-layout">
<div style="margin: 30px auto; padding: 30px 80px; background-color: white;">
<div class="logo flex-layout">
<img src="../icon.png" alt="rust-search-extension-logo">
Rust Search Extension
</div>
<div style="flex-direction: column;" class="flex-layout">
<h1>Searching Statistics</h1>
<div class="chart-heatmap"></div>
</div>
</div>
</div>
</body>
<script type="text/javascript" src="index.js"></script>
</html>

28
extension/stats/index.js Normal file
View file

@ -0,0 +1,28 @@
let now = moment().endOf('day').toDate();
let yearAgo = moment().startOf('day').subtract(1, 'year').toDate();
let chartData = [];
let history = JSON.parse(localStorage.getItem("history"));
history.forEach(element => {
chartData.push({
date: new Date(element.time),
count: 1
})
});
let heatmap = calendarHeatmap()
.data(chartData)
.selector('.chart-heatmap')
.tooltipEnabled(true)
.colorRange(['#f4f7f7','#F9BB2D'])
.tooltipUnit([
{ min: 0, unit: 'searching' },
{ min: 1, max: 1, unit: 'searching' },
{ min: 2, max: 'Infinity', unit: 'searchings' }
])
.legendEnabled(true)
.onClick(function (data) {
console.log('data', data);
});
heatmap();