[Widget]Trilium-Heatmap #4213
Replies: 9 comments 14 replies
-
I've always wanted a widget like this |
Beta Was this translation helpful? Give feedback.
-
This is really cool! I just have a question about runOnServer.js. If I add this, should I then disable |
Beta Was this translation helpful? Give feedback.
-
This widget is so cool! Keep up the great work! I love adding it to other notes (especially my default root note) by 'Including' it 👍 |
Beta Was this translation helpful? Give feedback.
-
Hi, that's very cool. I admit I had a very similar widget like this for years, but it's been so ugly I've never shared it. This one looks much better. I have two suggestions for querying data from the database:
Combining those two points, you can make a single query to count everything: SELECT COUNT(*), date FROM (
SELECT noteId, SUBSTR(utcDateModified, 0, 11) AS date FROM notes
UNION
SELECT DISTINCT noteId, SUBSTR(utcDateCreated, 0, 11) AS date FROM revisions
)
GROUP BY date On my pretty large database the query takes 100 milliseconds, so it's likely fast enough to not have to save/cache the results in a data note. (the query uses schema from 0.61 and won't work on 0.60 and older) |
Beta Was this translation helpful? Give feedback.
-
It's on fire. |
Beta Was this translation helpful? Give feedback.
-
I like the idea of releasing a |
Beta Was this translation helpful? Give feedback.
-
I have modified it with my custom stylizing, and merge 3 js files into ones. Here is what it look like: Here is the HTML code: <!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<style>
#heatmap-tooltip {
padding: 8px;
font-size: 13px;
line-height: 1.5;
text-align: center;
color: #FFFFFF;
background: #24292F;
display: none;
position: absolute;
border-radius: 8px;
box-sizing: border-box;
}
</style>
</head>
<body>
<div style="display: flex; justify-content: center; align-items: center;">
<svg id="trilium-heatmap"></svg>
</div>
<div id="heatmap-tooltip"></div>
</body>
</html> And here is my js code: The original version clicked on each square to jump to the notes for the day, but there were times when there were no notes for the day, so that's been fixed over here. /******************** Custom Parameters ********************/
const dataRange = [1, 32];
const colorRange = ["#9be9a8", "#216e39"];
const displayWeekTable = ['Mon', 'Wed', 'Fri'];
const displayMonthTable = [ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' ];
/******************** Drawing Parameters ********************/
const borderMargin = 16; const titleMargin = 8;
const monthBoxSize = 13; const boxSize = 13; const boxMargin = 3;
const weekBoxSize = boxSize * 3;
const svgWidth = borderMargin * 2 + weekBoxSize + titleMargin + 52 * boxSize + 51 * boxMargin;
const svgHeight = borderMargin * 2 + monthBoxSize + titleMargin + 7 * boxSize + 6 * boxMargin;
/******************** Drawing Handles ********************/
const svg = d3.select('#trilium-heatmap').attr('width', svgWidth).attr('height', svgHeight);
const drawWeeks = svg.append('g').attr(
'transform', `translate(${borderMargin + titleMargin}, ${borderMargin + monthBoxSize + titleMargin})`
);
const drawMonths = svg.append('g').attr(
'transform', `translate(${borderMargin + weekBoxSize + titleMargin}, ${borderMargin})`
);
const drawBoxes = svg.append('g').attr(
'transform', `translate(${borderMargin + weekBoxSize + titleMargin}, ${borderMargin + monthBoxSize + titleMargin})`
);
/******************** Dataset ********************/
const notesData = {};
const notes = await api.runOnBackend(async() => {
const results = api.sql.getRows(`SELECT date as notesDate, COUNT(*) as notesCount FROM (
SELECT noteId, SUBSTR(utcDateModified, 0, 11) AS date FROM notes
UNION
SELECT DISTINCT noteId, SUBSTR(utcDateCreated, 0, 11) AS date FROM notes
)
GROUP by date`);
return results;
});
for (let i = 0; i < notes.length; i++) {
notesData[notes[i]['notesDate']] = notes[i]['notesCount']
}
/******************** Drawing ********************/
const totalDays = new Date(new Date().getFullYear(), 1, 29).getDate() === 29 ? 366 : 365;
const colorScale = d3.scaleSequential().domain(dataRange).interpolator(d3.interpolateRgb(colorRange[0], colorRange[1]));
const startDate = new Date();
startDate.setDate(new Date().getDate() - totalDays + 1);
const startDayOfWeek = startDate.getDay();
// Draw Weeks
for (let i = 0; i < 3; i++) {
drawWeeks.append('text').text(displayWeekTable[i])
.attr('week', displayWeekTable[i])
.attr('font-size', '13px')
.attr('fill', '#1F2328')
.attr('x', 0)
.attr('y', (boxSize + boxMargin) * (2 * i + 1) + boxSize);
}
// Draw Boxes
let currentX = 0; let currentY = startDayOfWeek;
for (let i = totalDays - 1; i >= 0; i--) {
let referDate = new Date();
referDate.setDate(referDate.getDate() - i);
drawBoxes.append('rect').attr('date', referDate.toISOString().slice(0, 10))
.attr('width', boxSize).attr('height', boxSize)
.attr('rx', boxMargin).attr('ry', boxMargin)
.attr('x', (boxSize + boxMargin) * currentX).attr('y', (boxSize + boxMargin) * currentY)
.attr('fill', () => {
const referDateFormatted = referDate.toISOString().slice(0, 10);
const notesCount = parseInt(notesData[referDateFormatted]);
if (isNaN(notesCount)) {
return '#ebedf0';
} else if (notesCount <= dataRange[1]) {
return colorScale(notesCount - 1);
} else {
return colorRange[1];
}
})
.on('mouseover', function(event) {
const toolTips = document.getElementById('heatmap-tooltip');
const heatMapDiv = document.getElementById('heatmap-tooltip').parentElement;
const referDateFormatted = this.getAttribute('date');
const notesCount = parseInt(notesData[referDateFormatted]);
let bounds = heatMapDiv.getBoundingClientRect();
d3.select(this).style('stroke', 'black').style('stroke-width', 0.75);
console.log(event.clientX + " " + event.clientY);
toolTips.innerHTML = `${!isNaN(notesCount) ? notesCount.toString() : 'No'} notes modified in ${referDateFormatted}`;
toolTips.style.left = `${event.clientX - bounds.left - 96}px`;
toolTips.style.top = `${event.clientY - bounds.top + 12}px`;
toolTips.style.display = 'block';
})
.on('mouseout', function() {
const toolTips = document.getElementById('heatmap-tooltip');
d3.select(this).style("stroke", null);
toolTips.style.display = 'none';
})
.on("click", async function() {
const referDateFormatted = this.getAttribute('date');
const notesCount = parseInt(notesData[referDateFormatted]);
if (!isNaN(notesCount)) {
const noteId = await api.runOnBackend(async (referDateFormatted) => {
const note = await api.getDayNote(referDateFormatted);
return note.noteId;
}, [referDateFormatted]);
await api.waitUntilSynced();
api.activateNote(noteId);
}
});
if (referDate.getDate() == 1) {
drawMonths.append('text').text(displayMonthTable[referDate.getMonth()])
.attr('month', displayMonthTable[referDate.getMonth()])
.attr('font-size', '13px')
.attr('fill', '#1F2328')
.attr('x', (boxSize + boxMargin) * currentX)
.attr('y', monthBoxSize);
}
currentY = (currentY + 1) % 7;
currentX = currentX + (currentY == 0);
}
// Draw Months
for (let i = 0; i < totalDays; i++) {
let referDate = new Date();
referDate.setDate(referDate.getDate() - i);
currentY = (currentY + 1) % 7;
currentX = currentX + (currentY == 0);
} |
Beta Was this translation helpful? Give feedback.
-
I like the idea of a plugin system as well. The possible tasks that are taken for installing an extension currently are:
Some of these arepotentially super dangerous:
A lot of these issues could be mitigated with a bit of coding or some minor tweaks to Trilium (example: the ability to limit the tree scope of #appCss would be amazing. i.e. #appCss=2 results in the Css only being applied at it's own level and it's parent level. I think this could be a really fun project. If anyone starts working on this I would be happy to help. |
Beta Was this translation helpful? Give feedback.
-
very cool! |
Beta Was this translation helpful? Give feedback.
-
Display the number of note edits in Trilium, just like the GitHub contributions heatmap.
https://github.com/dvai/Trilium-Heatmap
Beta Was this translation helpful? Give feedback.
All reactions