### 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

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' } } } } }; ```