### Content Optimization Suggestions Format
Source: https://github.com/marvomatic/n8n-templates/blob/main/gsc-ai-seo-writer/readme.md
This outlines the structure of the HTML report generated with content optimization suggestions. It includes placeholders for original text and improved text, guiding content creators on how to enhance their articles.
```HTML
Content Optimization Report
Optimization Suggestions
Optimized Titles
Suggested Title 1
Suggested Title 2
Improved Meta Descriptions
Suggested Meta Description 1
High-Performing Keywords
- Keyword A (Impressions: X, Clicks: Y)
- Keyword B (Impressions: Z, Clicks: W)
Content Rewriting Suggestions
Section: Introduction
Original Text: "some text of your article"
New Text: "Text that is better optimized based on the data of your website"
Section: Main Points
Original Text: "another paragraph to improve"
New Text: "Revised paragraph with better keyword integration and clarity"
```
--------------------------------
### Setup Table Sorting (JavaScript)
Source: https://github.com/marvomatic/n8n-templates/blob/main/website-seo-audit/website-seo-audit-report.html
Attaches click event listeners to table header cells (``) that have a `data-sort` attribute. When clicked, it determines the sort key and direction, updates the UI to indicate the active sort column and direction, and initiates the table sorting process.
```javascript
// Set up sorting functionality
function setupSorting() {
document.querySelectorAll('th[data-sort]').forEach(th => {
th.addEventListener('click', () => {
const tableId = th.closest('table').id;
const sortKey = th.dataset.sort;
const currentDirection = th.dataset.direction || 'asc';
const newDirection = currentDirection === 'asc' ? 'desc' : 'asc';
// Update direction on all headers
th.closest('tr').querySelectorAll('th').forEach(header => {
header.dataset.direction = header === th ? newDirection : '';
header.classList.remove('sort-asc', 'sort-desc');
});
// Add sort indicator
th.classList.add(`sort-${newDirection}`);
// Sort the data
sortTable(tableId, sortKey, newDirection);
});
});
}
```
--------------------------------
### Smooth Scrolling for Anchor Links
Source: https://github.com/marvomatic/n8n-templates/blob/main/tracked-keyword-performance-report-generator/n8n-keyword-rank-tracking-example-report.html
Implements smooth scrolling for all anchor links (`` tags with `href` starting with '#'). It prevents default behavior, finds the target element, scrolls to it with an offset for header height, and updates the browser history.
```javascript
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
anchor.addEventListener('click', function(e) {
e.preventDefault();
const targetId = this.getAttribute('href');
const targetElement = document.querySelector(targetId);
if (targetElement) {
window.scrollTo({
top: targetElement.offsetTop - 70,
behavior: 'smooth'
});
history.pushState(null, null, targetId);
}
});
});
```
--------------------------------
### Second Line Chart Configuration (Chart.js)
Source: https://github.com/marvomatic/n8n-templates/blob/main/tracked-keyword-performance-report-generator/n8n-keyword-rank-tracking-example-report.html
This snippet provides a second Chart.js configuration for a line chart, also displaying 'Position', 'Clicks', and 'CTR (%)' data with separate Y-axes. It includes similar customization for tooltips and chart options, targeting a canvas element with id 'chart-2'. The data points and specific Y-axis maximums differ from the first example.
```javascript
document.addEventListener('DOMContentLoaded', function() {
const ctx = document.getElementById('chart-2');
if (!ctx) return;
const formattedDates = ["2025-03-26","2025-03-27","2025-03-28","2025-03-29","2025-03-30","2025-03-31","2025-04-01"].map(date => {
const d = new Date(date);
return d.toLocaleDateString('de-DE', { month: 'short', day: 'numeric' });
});
new Chart(ctx, {
type: 'line',
data: {
labels: formattedDates,
datasets: [
{
label: 'Position',
data: [2.3339208093302766,1.9879622914682544,3.5105255231330803,3.4441061557397807,2.013415944594291,1.9902074812857853,4.4685700519097855],
borderColor: '#ff6d5a',
backgroundColor: 'rgba(255, 109, 90, 0.1)',
tension: 0.1,
fill: true,
pointRadius: 4,
pointHoverRadius: 6,
yAxisID: 'y'
},
{
label: 'Clicks',
data: [197,193,153,274,262,162,169],
borderColor: '#4e79a7',
backgroundColor: 'rgba(78, 121, 167, 0.1)',
tension: 0.1,
fill: false,
pointRadius: 3,
pointHoverRadius: 5
}
]
},
options: {
responsive: true,
maintainAspectRatio: false,
interaction: {
mode: 'index',
intersect: false,
},
plugins: {
tooltip: {
callbacks: {
label: function(context) {
let label = context.dataset.label || '';
if (label) {
label += ': ';
}
if (context.dataset.label === 'Position') {
return label + context.parsed.y.toFixed(2);
} else if (context.dataset.label === 'CTR (%)') {
return label + context.parsed.y.toFixed(2) + '%';
} else {
return label + context.parsed.y;
}
}
}
},
legend: {
position: 'top',
labels: {
usePointStyle: true,
padding: 15
}
},
},
scales: {
y: {
type: 'linear',
display: true,
position: 'left',
reverse: true,
title: {
display: true,
text: 'Position',
color: '#909399'
},
grid: {
color: '#f5f5f5'
},
min: 1,
max: Math.ceil(Math.max(...[2.3339208093302766,1.9879622914682544,3.5105255231330803,3.4441061557397807,2.013415944594291,1.9902074812857853,4.4685700519097855]) + 0.5)
},
y1: {
type: 'linear',
display: false,
position: 'right',
title: {
display: true,
text: 'Clicks',
color: '#909399'
},
grid: {
drawOnChartArea: false
},
min: 0
},
y2: {
type: 'linear',
display: false,
position: 'right',
title: {
display: true,
text: 'CTR (%)',
color: '#909399'
},
grid: {
drawOnChartArea: false
},
min: 0
},
x: {
title: {
display: true,
text: 'Date',
color: '#909399'
},
grid: {
color: '#f5f5f5'
}
}
}
}
});
});
```
--------------------------------
### Initialize Chart.js bar charts for keyword data
Source: https://github.com/marvomatic/n8n-templates/blob/main/website-seo-audit/website-seo-audit-report.html
This JavaScript code initializes two stacked bar charts using the Chart.js library, one for desktop keyword data and another for mobile. Each chart visualizes keyword positions across 'Current' and 'Previous' periods, categorized into 'Top 3', 'Positions 4-10', 'Positions 11-20', and 'Below Top 20'. The charts are responsive and configured with custom legend and tooltip options.
```JavaScript
document.addEventListener('DOMContentLoaded', function() {
// Chart for DESKTOP
const desktopKeywordChartElement = document.getElementById('desktopKeywordChart');
if (desktopKeywordChartElement) {
new Chart(desktopKeywordChartElement, {
type: 'bar',
data: {
labels: ['Current', 'Previous'],
datasets: [
{
label: 'Top 3',
data: [1861, 2267],
backgroundColor: '#4CAF50',
borderColor: '#4CAF50',
borderWidth: 1
},
{
label: 'Positions 4-10',
data: [3271, 3404],
backgroundColor: '#2196F3',
borderColor: '#2196F3',
borderWidth: 1
},
{
label: 'Positions 11-20',
data: [397, 337],
backgroundColor: '#FF9800',
borderColor: '#FF9800',
borderWidth: 1
},
{
label: 'Below Top 20',
data: [1081, 784],
backgroundColor: '#F44336',
borderColor: '#F44336',
borderWidth: 1
}
]
},
options: {
indexAxis: 'y',
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: 'bottom',
labels: {
boxWidth: 12,
padding: 15,
font: {
size: 12
}
}
},
tooltip: {
callbacks: {
label: function(context) {
const label = context.dataset.label || '';
const value = context.raw || 0;
return `${label}: ${value}`;
}
}
}
},
scales: {
x: {
stacked: true,
grid: {
display: false
}
},
y: {
stacked: true,
grid: {
display: false
}
}
}
}
});
}
// Chart for MOBILE
const mobileKeywordChartElement = document.getElementById('mobileKeywordChart');
if (mobileKeywordChartElement) {
new Chart(mobileKeywordChartElement, {
type: 'bar',
data: {
labels: ['Current', 'Pre
```
--------------------------------
### Keyword Ranking Pie Chart
Source: https://github.com/marvomatic/n8n-templates/blob/main/tracked-keyword-performance-report-generator/n8n-keyword-rank-tracking-example-report.html
Renders a pie chart using the Chart.js library to visualize keyword ranking distribution (Improved, Stable, Declined). It configures the chart with specific data, colors, and tooltip callbacks for displaying percentages.
```javascript
document.addEventListener('DOMContentLoaded', function() {
const improvedCount = 12;
const stableCount = 0;
const declinedCount = 8;
const ctx = document.getElementById('keywordRankingPieChart').getContext('2d');
new Chart(ctx, {
type: 'pie',
data: {
labels: ['Improved', 'Stable', 'Declined'],
datasets: [{
data: [improvedCount, stableCount, declinedCount],
backgroundColor: [
'#4CAF50',
'#2196F3',
'#F44336'
],
borderColor: 'white',
borderWidth: 2
}]
},
options: {
responsive: true,
maintainAspectRatio: true,
plugins: {
legend: {
position: 'bottom',
labels: {
padding: 20,
font: {
size: 14
}
}
},
tooltip: {
callbacks: {
label: function(context) {
const label = context.label || '';
const value = context.parsed || 0;
const total = context.dataset.data.reduce((a, b) => a + b, 0);
const percentage = ((value * 100) / total).toFixed(1);
return `${label}: ${value} (${percentage}%)`;
}
}
}
}
}
});
});
```
--------------------------------
### n8n-templates UI Stylesheet
Source: https://github.com/marvomatic/n8n-templates/blob/main/tracked-keyword-performance-report-generator/n8n-keyword-rank-tracking-example-report.html
Comprehensive CSS for styling the n8n-templates project, covering layout, navigation, interactive elements like accordions, and responsive design adjustments for various screen sizes.
```css
/* General styles */
:root {
--primary-color: #4CAF50;
--secondary-color: #6c757d;
--light-color: #f8f9fa;
--dark-color: #343a40;
--border-color: #dee2e6;
--card-bg: #ffffff;
--table-row-hover-bg: #e9ecef;
}
body {
font-family: 'Arial', sans-serif;
line-height: 1.6;
color: var(--dark-color);
background-color: var(--light-color);
margin: 0;
padding: 0;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 2rem 1rem;
}
/* Header and Navigation */
.header {
background: var(--card-bg);
padding: 1rem 0;
border-bottom: 1px solid var(--border-color);
margin-bottom: 2rem;
}
.header-content {
display: flex;
justify-content: space-between;
align-items: center;
}
.logo img {
height: 40px;
}
.nav-container {
display: flex;
align-items: center;
}
.nav-links {
list-style: none;
display: flex;
gap: 1.5rem;
margin: 0;
padding: 0;
}
.nav-links li {
position: relative;
}
.nav-links a {
text-decoration: none;
color: var(--dark-color);
font-weight: 500;
transition: color 0.3s ease;
}
.nav-links a:hover {
color: var(--primary-color);
}
.dropdown {
position: absolute;
top: 100%;
left: 0;
background: var(--card-bg);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
border-radius: 4px;
min-width: 200px;
z-index: 1000;
opacity: 0;
visibility: hidden;
transform: translateY(10px);
transition: opacity 0.3s ease, visibility 0.3s ease, transform 0.3s ease;
}
.nav-links li:hover .dropdown {
opacity: 1;
visibility: visible;
transform: translateY(0);
}
.dropdown li {
margin: 0;
}
.dropdown a {
padding: 0.75rem 1rem;
display: block;
font-weight: normal;
}
.dropdown a:hover {
background: var(--table-row-hover-bg);
}
/* Tab styles */
.tabs {
display: flex;
gap: 0.5rem;
margin-bottom: 1.5rem;
overflow-x: auto;
padding-bottom: 0.5rem;
}
.tablinks {
padding: 0.75rem 1.5rem;
cursor: pointer;
border: none;
background: var(--light-color);
color: var(--dark-color);
border-radius: 4px;
font-weight: 500;
white-space: nowrap;
transition: all 0.3s ease;
margin-bottom: 0.5rem;
}
.tablinks:hover {
background: var(--primary-color);
color: white;
}
.tablinks.active {
background: var(--primary-color);
color: white;
}
.tabcontent {
display: none;
padding: 1rem 0;
width: 100%;
}
/* Accordion styles */
.accordion-item {
border: 1px solid var(--border-color);
border-radius: 8px;
margin-bottom: 1rem;
overflow: hidden;
width: 100%;
}
.accordion-header {
padding: 1rem;
background: var(--light-color);
cursor: pointer;
display: flex;
justify-content: space-between;
align-items: center;
transition: background-color 0.3s ease;
}
.accordion-header:hover {
background: var(--table-row-hover-bg);
}
.accordion-header::after {
content: '+';
font-size: 1.5rem;
font-weight: bold;
transition: transform 0.3s ease;
}
.accordion-item.active .accordion-header::after {
transform: rotate(45deg);
}
.accordion-content {
max-height: 0;
overflow: hidden;
transition: max-height 0.3s ease-out;
background: var(--card-bg);
width: 100%;
}
.accordion-item.active .accordion-content {
max-height: 1500px;
}
/* Metrics display */
.keyword-metrics {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 1rem;
padding: 1rem;
}
.metric-card {
background: var(--light-color);
padding: 1rem;
border-radius: 4px;
text-align: center;
}
.metric-value {
font-size: 1.5rem;
font-weight: bold;
color: var(--primary-color);
}
.metric-label {
color: var(--secondary-color);
font-size: 0.9rem;
}
/* Responsive styles */
@media (max-width: 1280px) {
.container {
padding: 0 1rem;
}
.nav-links {
gap: 1rem;
}
.keyword-metrics {
grid-template-columns: repeat(2, 1fr);
}
}
@media (max-width: 992px) {
.chart-container canvas {
max-height: 400px;
}
.chart-container {
min-height: 250px;
}
}
@media (max-width: 768px) {
.header-content {
flex-direction: column;
align-items: flex-start;
}
.tabs {
flex-direction: column;
gap: 0.5rem;
}
.nav-container {
width: 100%;
margin-top: 1rem;
}
.nav-links {
flex-direction: column;
width: 100%;
}
.dropdown {
position: static;
width: 100%;
box-shadow: none;
opacity: 1;
visibility: visible;
transform: none;
max-height: 0;
overflow: hidden;
transition: max-height 0.3s ease;
}
.nav-links > li:hover .dropdown {
max-height: 500px;
}
.keyword-metrics {
grid-template-columns: 1fr;
}
table {
display: block;
overflow-x: auto;
width: 100%;
}
table th,
table td {
min-width: 80px;
}
.chart-container {
margin: 1rem 0;
padding: 0.5rem;
}
.chart-container canvas {
height: auto !important;
max-height: 300px;
}
.chart-container canvas {
max-height: 250px;
}
/* Mobile menu toggle button */
.mobile-menu-toggle {
display: block;
position: absolute;
top: 1rem;
right: 1rem;
}
.nav-container {
display: none;
}
.nav-container.active {
display: block;
}
}
@media (max-width: 480px) {
.chart-container {
min-height: 250px;
}
.chart-container canvas {
max-height: 200px;
}
.chart-container {
min-height: 200px;
}
.chart-container canvas {
max-height: 180px;
}
.tablinks {
width: 100%;
text-align: center;
}
.summary-item span {
font-size: 1.2rem;
}
}
/* Additional CSS for the new structure */
.metrics-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 1rem;
margin-bottom: 2rem;
}
.summary-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 1rem;
padding: 1rem 0;
}
.summary-item {
text-align: center;
font-size: 1.1rem;
}
.summary-item span {
display: block;
font-size: 1.5rem;
margin-bottom: 0.5rem;
}
/* Template styles */
template {
display: none;
}
/* Responsive adjustments */
@media (max-width: 768px) {
.metrics-grid {
grid-template-columns: repeat(2, 1fr);
}
.summary-grid {
grid-template-columns: 1fr;
}
}
@media (max-width: 480px) {
.metrics-grid {
grid-template-columns: 1fr;
}
}
/* Mobile menu toggle button */
.mobile-menu-toggle {
display: none;
background: none;
border: none;
font-size: 1.5rem;
cursor: pointer;
color: var(--primary-color);
}
```
--------------------------------
### Chart.js Performance Metrics Chart (performanceChart)
Source: https://github.com/marvomatic/n8n-templates/blob/main/tracked-keyword-performance-report-generator/n8n-keyword-rank-tracking-example-report.html
Initializes a Chart.js line chart for performance data, targeting the 'performanceChart' canvas. It includes configurations for 'Position' data and sets up axes and chart options. The provided snippet is incomplete, showing only the 'Position' dataset.
```javascript
document.addEventListener('DOMContentLoaded', function() {
const ctx = document.getElementById('performanceChart');
if (!ctx) return;
const formattedDates = ["2025-03-26","2025-03-27","2025-03-28","2025-03-29","2025-03-30","2025-03-31","2025-04-01"].map(date => {
const d = new Date(date);
return d.toLocaleDateString('de-DE', { month: 'short', day: 'numeric' });
});
new Chart(ctx, {
type: 'line',
data: {
labels: formattedDates,
datasets: [
{
label: 'Position',
data: [4.654300319967094,4.338390599759229,4.496270241512782,4.7784117598868985,4.877307182913574,4.799278104368175,4.433395493234575],
borderColor: '#ff6d5a',
backgroundColor: 'rgba(255, 109, 90, 0.1)',
tension: 0.1,
f
```
--------------------------------
### Data Table Initialization and Management
Source: https://github.com/marvomatic/n8n-templates/blob/main/website-seo-audit/website-seo-audit-report.html
JavaScript functions for initializing data tables, setting up filtering mechanisms (text inputs and dropdowns), and managing pagination state. It processes data from `tableData` and applies filtering logic.
```javascript
const tableData = { keyword_movement: [ /* ... data ... */ ], traffic_performance_by_country: [ /* ... data ... */ ], performance_by_pages: [ { "Performance Category": "Low Performer", "URL": "https://domain.com/article/", "Current Clicks": "533", "Previous Clicks": "752", "Clicks Change": "-219", "Clicks Change (%)": "-29.122340425531917", "Current Impressions": "27274", "Previous Impressions": "34353", "Impressions Change": "-7079", "Impressions Change (%)": "-20.606642796844525", "Current Avg Position": "7.23020354080963", "Previous Avg Position": "6.526822044892289", "Position Change": "-0.7033814959173412", "Position Change (%)": "-10.776783725362762", "Current CTR": "0.019542421353670163", "Previous CTR": "0.021890373475387883", "CTR Change": "-0.0023479521217177196", "CTR Change (%)": "-10.725957345394791" }, { "Performance Category": "Low Performer", "URL": "https://domain.com/article/", "Current Clicks": "169", "Previous Clicks": "253", "Clicks Change": "-84", "Clicks Change (%)": "-33.201581027667984", "Current Impressions": "3629", "Previous Impressions": "5361", "Impressions Change": "-1732", "Impressions Change (%)": "-32.30740533482559", "Current Avg Position": "20.877080619481834", "Previous Avg Position": "24.455802655841058", "Position Change": "3.578722036359224", "Position Change (%)": "14.633427030474", "Current CTR": "0.04656930283824745", "Previous CTR": "0.04719268793135609", "CTR Change": "-6.233850931086429E-4", "CTR Change (%)": "-1.320935764488314" }, { "Performance Category": "Low Performer", "URL": "https://domain.com/article/", "Current Clicks": "246", "Previous Clicks": "327", "Clicks Change": "-81", "Clicks Change (%)": "-24.770642201834864", "Current Impressions": "11777", "Previous Impressions": "13137", "Impressions Change": "-1360", "Impressions Change (%)": "-10.352439674202634", "Current Avg Position": "36.08330198200949", "Previous Avg Position": "37.66649440307547", "Position Change": "1.5831924210659807", "Position Change (%)": "4.203184942362762", "Current CTR": "0.020888171860405876", "Previous CTR": "0.024891527746060745", "CTR Change": "-0.004003355885654869", "CTR Change (%)": "-16.083206810351076" } ] }; // Pagination state const paginationState = { keyword_movement: { currentPage: 1, pageSize: 10, filteredData: [] }, traffic_performance_by_country: { currentPage: 1, pageSize: 10, filteredData: [] }, performance_by_pages: { currentPage: 1, pageSize: 10, filteredData: [] } }; // Initialize tables function initializeTables() { Object.keys(tableData).forEach(tableId => { // Initialize filteredData with all data paginationState[tableId].filteredData = tableData[tableId]; renderFilteredTable(tableId); populateDropdown(tableId); renderPagination(tableId); }); // Set up filtering setupFilters(); // Set up sorting setupSorting(); } // Set up filtering for all tables function setupFilters() { // Text input filters document.querySelectorAll('.filter-input').forEach(input => { input.addEventListener('input', event => { const tableId = event.target.dataset.tableId; filterTable(tableId); }); }); // Dropdown filters document.querySelectorAll('select[data-table-id]').forEach(select => { select.addEventListener('change', event => { const tableId = event.target.dataset.tableId; filterTable(tableId); }); }); } // Apply all active filters to a table function filterTable(tableId) { // Get all filter inputs for this table const textFilters = Array.from(document.querySelectorAll(`.filter-input[data-table-id="${tableId}"]`)) .map(input => ({ value: input.value.toLowerCase(), id: input.id })) .filter(filter => filter.value); const dropdownFilters = Array.from(document.querySelectorAll(`select[data-table-id="${tableId}"]`)) .map(select => ({ value: select.value, id: select.id })) .filter(filter => filter.value); // If no filters are active, show all data if (textFilters.length === 0 && dropdownFilters.length === 0) { paginationState[tableId].filteredData = tableData[tableId]; paginationState[tableId].currentPage = 1; renderFilteredTable(tableId); renderPagination(tableId); return; } // Apply filters to the data const filteredData = tableData[tableId].filter(row => { // Check text filters (search across all fields) const textFilterMatch = textFilters.every(filter => { const searchText = filter.value; // Special case for URL filter - only search in URL field if (filter.id === 'url-filter') { const urlField = Object.keys(row).find(key => key === 'URL' || key.toLowerCase() === 'url' ); return urlField && String(row[urlField]).toLowerCase().includes(searchText); } // Generic search across all fields return Object.values(row).some(value => String(value).toLowerCase().includes(searchText)); }); // Check dropdown filters const dropdownFilterMatch = dropdownFilters.every(filter => { // Find the key corresponding to the filter ID (e.g., 'Performance Category') const rowKey = Object.keys(row).find(key => key.toLowerCase() === filter.id.replace('-filter', '').toLowerCase()); return rowKey && String(row[rowKey]) === filter.value; }); return textFilterMatch && dropdownFilterMatch; }); paginationState[tableId].filteredData = filteredData; paginationState[tableId].currentPage = 1; renderFilteredTable(tableId); renderPagination(tableId); } // Placeholder functions for rendering and dropdown population // These would contain the actual DOM manipulation logic // function renderFilteredTable(tableId) { /* ... */ } // function populateDropdown(tableId) { /* ... */ } // function renderPagination(tableId) { /* ... */ } // function setupSorting() { /* ... */ } // Call initializeTables to set everything up // initializeTables();
```
--------------------------------
### Create Desktop Keyword Chart with Chart.js
Source: https://github.com/marvomatic/n8n-templates/blob/main/website-seo-audit/website-seo-audit-report.html
Generates a bar chart to visualize desktop keyword ranking data using Chart.js. It requires a canvas element with the ID 'desktopKeywordChart' and the Chart.js library to be loaded. The chart displays data categorized by ranking positions for 'Current' and 'Previous' periods.
```javascript
const desktopKeywordChartElement = document.getElementById('desktopKeywordChart');
if (desktopKeywordChartElement) {
new Chart(desktopKeywordChartElement, {
type: 'bar',
data: {
labels: ['Current', 'Previous'],
datasets: [
{
label: 'Top 3',
data: [2417, 2749],
backgroundColor: '#4CAF50',
borderColor: '#4CAF50',
borderWidth: 1
},
{
label: 'Positions 4-10',
data: [2906, 3553],
backgroundColor: '#2196F3',
borderColor: '#2196F3',
borderWidth: 1
},
{
label: 'Positions 11-20',
data: [271, 291],
backgroundColor: '#FF9800',
borderColor: '#FF9800',
borderWidth: 1
},
{
label: 'Below Top 20',
data: [136, 122],
backgroundColor: '#F44336',
borderColor: '#F44336',
borderWidth: 1
}
]
},
options: {
indexAxis: 'y',
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: 'bottom',
labels: {
boxWidth: 12,
padding: 15,
font: {
size: 12
}
}
},
tooltip: {
callbacks: {
label: function(context) {
const label = context.dataset.label || '';
const value = context.raw || 0;
return `${label}: ${value}`;
}
}
}
},
scales: {
x: {
stacked: true,
grid: {
display: false
}
},
y: {
stacked: true,
grid: {
display: false
}
}
}
}
});
}
```
--------------------------------
### Chart.js Performance Metrics Chart (Chart 1)
Source: https://github.com/marvomatic/n8n-templates/blob/main/tracked-keyword-performance-report-generator/n8n-keyword-rank-tracking-example-report.html
Configures a Chart.js line chart to display 'Position', 'Clicks', and 'CTR (%)' metrics. It sets up multiple Y-axes for different data scales and customizes tooltips and legends. The chart is initialized when the DOM is fully loaded.
```javascript
document.addEventListener('DOMContentLoaded', function() {
const ctx = document.getElementById('chart-1');
if (!ctx) return;
const formattedDates = ["2025-03-26","2025-03-27","2025-03-28","2025-03-29","2025-03-30","2025-03-31","2025-04-01"].map(date => {
const d = new Date(date);
return d.toLocaleDateString('de-DE', { month: 'short', day: 'numeric' });
});
new Chart(ctx, {
type: 'line',
data: {
labels: formattedDates,
datasets: [
{
label: 'Position',
data: [3.2398825230819503,3.288224825568806,5.19346865080743,3.15796227747185,3.980547504668893,5.083308728999997,2.979946005167351],
borderColor: '#ff6d5a',
backgroundColor: 'rgba(255, 109, 90, 0.1)',
tension: 0.1,
fill: true,
pointRadius: 4,
pointHoverRadius: 6,
yAxisID: 'y'
},
{
label: 'Clicks',
data: [67,118,104,96,115,56,130],
borderColor: '#4e79a7',
backgroundColor: 'rgba(78, 121, 167, 0.1)',
tension: 0.1,
fill: false,
pointRadius: 3,
pointHoverRadius: 5,
yAxisID: 'y1',
hidden: true
},
{
label: 'CTR (%)',
data: [4.345006485084306,5.490926012098651,4.733727810650888,5.283434232250963,8.96336710833983,2.198665096191598,9.900990099009901],
borderColor: '#59a14f',
backgroundColor: 'rgba(89, 161, 79, 0.1)',
tension: 0.1,
fill: false,
pointRadius: 3,
pointHoverRadius: 5,
yAxisID: 'y2',
hidden: true
}
]
},
options: {
responsive: true,
maintainAspectRatio: false,
interaction: {
mode: 'index',
intersect: false,
},
plugins: {
tooltip: {
callbacks: {
label: function(context) {
let label = context.dataset.label || '';
if (label) {
label += ': ';
}
if (context.dataset.label === 'Position') {
return label + context.parsed.y.toFixed(2);
} else if (context.dataset.label === 'CTR (%)') {
return label + context.parsed.y.toFixed(2) + '%';
} else {
return label + context.parsed.y;
}
}
}
},
legend: {
position: 'top',
labels: {
usePointStyle: true,
padding: 15
}
},
},
scales: {
y: {
type: 'linear',
display: true,
position: 'left',
reverse: true,
title: {
display: true,
text: 'Position',
color: '#909399'
},
grid: {
color: '#f5f5f5'
},
min: 1,
max: Math.ceil(Math.max(...[3.2398825230819503,3.288224825568806,5.19346865080743,3.15796227747185,3.980547504668893,5.083308728999997,2.979946005167351]) + 0.5)
},
y1: {
type: 'linear',
display: false,
position: 'right',
title: {
display: true,
text: 'Clicks',
color: '#909399'
},
grid: {
drawOnChartArea: false
},
min: 0
},
y2: {
type: 'linear',
display: false,
position: 'right',
title: {
display: true,
text: 'CTR (%)',
color: '#909399'
},
grid: {
drawOnChartArea: false
},
min: 0
},
x: {
title: {
display: true,
text: 'Date',
color: '#909399'
},
grid: {
color: '#f5f5f5'
}
}
}
}
});
});
```
--------------------------------
### Create Tablet Keyword Chart with Chart.js
Source: https://github.com/marvomatic/n8n-templates/blob/main/website-seo-audit/website-seo-audit-report.html
Generates a bar chart to visualize tablet keyword ranking data using Chart.js. It requires a canvas element with the ID 'tabletKeywordChart' and the Chart.js library to be loaded. The chart displays data categorized by ranking positions for 'Current' and 'Previous' periods, similar to the desktop chart but with different data values.
```javascript
const tabletKeywordChartElement = document.getElementById('tabletKeywordChart');
if (tabletKeywordChartElement) {
new Chart(tabletKeywordChartElement, {
type: 'bar',
data: {
labels: ['Current', 'Previous'],
datasets: [
{
label: 'Top 3',
data: [116, 121],
backgroundColor: '#4CAF50',
borderColor: '#4CAF50',
borderWidth: 1
},
{
label: 'Positions 4-10',
data: [116, 81],
backgroundColor: '#2196F3',
borderColor: '#2196F3',
borderWidth: 1
},
{
label: 'Positions 11-20',
data: [14, 14],
backgroundColor: '#FF9800',
borderColor: '#FF9800',
borderWidth: 1
},
{
label: 'Below Top 20',
data: [7, 1],
backgroundColor: '#F44336',
borderColor: '#F44336',
borderWidth: 1
}
]
},
options: {
indexAxis: 'y',
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: 'bottom',
labels: {
boxWidth: 12,
padding: 15,
font: {
size: 12
}
}
},
tooltip: {
callbacks: {
label: function(context) {
const label = context.dataset.label || '';
const value = context.raw || 0;
return `${label}: ${value}`;
}
}
}
},
scales: {
x: {
stacked: true,
grid: {
display: false
}
},
y: {
stacked: true,
grid: {
display: false
}
}
}
}
});
}
```
--------------------------------
### Configure Chart with Multiple Datasets and Axes
Source: https://github.com/marvomatic/n8n-templates/blob/main/tracked-keyword-performance-report-generator/n8n-keyword-rank-tracking-example-report.html
This JavaScript code configures a chart with three datasets: 'Position', 'Clicks', and 'CTR (%)'. It specifies styling, point radii, and assigns each dataset to a unique Y-axis ('y', 'y1', 'y2'). The configuration includes responsive options, custom tooltip callbacks for formatting values, and detailed axis titles and grid settings.
```javascript
const chartConfig = {
data: {
datasets: [
{
label: 'Position',
data: [4.654300319967094, 4.338390599759229, 4.496270241512782, 4.7784117598868985, 4.877307182913574, 4.799278104368175, 4.433395493234575],
borderColor: '#f9c74f',
backgroundColor: 'rgba(249, 199, 79, 0.1)',
tension: 0.1,
fill: true,
pointRadius: 4,
pointHoverRadius: 6,
yAxisID: 'y'
},
{
label: 'Clicks',
data: [2174, 2214, 2120, 2136, 2163, 1811, 1900],
borderColor: '#4e79a7',
backgroundColor: 'rgba(78, 121, 167, 0.1)',
tension: 0.1,
fill: false,
pointRadius: 3,
pointHoverRadius: 5,
yAxisID: 'y1',
hidden: true
},
{
label: 'CTR (%)',
data: [636.3644551862407, 669.2945095527193, 658.53578704165, 583.205651522527, 722.2182475546947, 539.963735278113, 565.5408140978582],
borderColor: '#59a14f',
backgroundColor: 'rgba(89, 161, 79, 0.1)',
tension: 0.1,
fill: false,
pointRadius: 3,
pointHoverRadius: 5,
yAxisID: 'y2',
hidden: true
}
]
},
options: {
responsive: true,
maintainAspectRatio: false,
interaction: {
mode: 'index',
intersect: false,
},
plugins: {
tooltip: {
callbacks: {
label: function(context) {
let label = context.dataset.label || '';
if (label) {
label += ': ';
}
if (context.dataset.label === 'Position') {
return label + context.parsed.y.toFixed(2);
} else if (context.dataset.label === 'CTR (%)') {
return label + context.parsed.y.toFixed(2) + '%';
} else {
return label + context.parsed.y;
}
}
}
},
legend: {
position: 'top',
labels: {
usePointStyle: true,
padding: 15
}
},
},
scales: {
y: {
type: 'linear',
display: true,
position: 'left',
reverse: true,
title: {
display: true,
text: 'Position',
color: '#909399'
},
grid: {
color: '#f5f5f5'
},
min: 1,
max: Math.ceil(Math.max(...[4.654300319967094,4.338390599759229,4.496270241512782,4.7784117598868985,4.877307182913574,4.799278104368175,4.433395493234575]) + 0.5)
},
y1: {
type: 'linear',
display: false,
position: 'right',
title: {
display: true,
text: 'Clicks',
color: '#909399'
},
grid: {
drawOnChartArea: false
},
min: 0
},
y2: {
type: 'linear',
display: false,
position: 'right',
title: {
display: true,
text: 'CTR (%)',
color: '#909399'
},
grid: {
drawOnChartArea: false
},
min: 0
},
x: {
title: {
display: true,
text: 'Date',
color: '#909399'
},
grid: {
color: '#f5f5f5'
}
}
}
}
};
``` |