Copie d'analyse des vols
Ceci est unMarket Research, Multimodal AIworkflow d'automatisation du domainecontenant 24 nœuds.Utilise principalement des nœuds comme If, Code, Switch, Telegram, HttpRequest. Visualisation des données de vols avec Chart.js, QuickChart API et un robot Telegram
- •Token Bot Telegram
- •Peut nécessiter les informations d'identification d'authentification de l'API cible
Nœuds utilisés (24)
Catégorie
{
"id": "yQaIw7M18cPt7vGT",
"meta": {
"instanceId": "5cee0adb1ef2b84ac8a86937fac5115d710898b6c70f9f7c3f3ca3ef70a11bf7",
"templateCredsSetupCompleted": true
},
"name": "Flight_Analytics copy",
"tags": [],
"nodes": [
{
"id": "7d5cc476-05c7-4c5f-a419-a60d3684a63d",
"name": "Telegram Trigger",
"type": "n8n-nodes-base.telegramTrigger",
"position": [
-1320,
-460
],
"webhookId": "04fc191f-7669-4134-a4c5-bd5ea2ab97ad",
"parameters": {
"updates": [
"message",
"callback_query"
],
"additionalFields": {}
},
"typeVersion": 1.1
},
{
"id": "0f89bae3-d898-4196-be65-685505c8e66e",
"name": "Vérifier Démarrage",
"type": "n8n-nodes-base.if",
"position": [
-1140,
-460
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "eae97e9f-8b8b-4432-bc12-67221d750682",
"operator": {
"name": "filter.operator.equals",
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.message.text }}",
"rightValue": "/start"
}
]
}
},
"typeVersion": 2.2
},
{
"id": "f0548827-9fad-4331-a361-b1f7c966a1e8",
"name": "Envoyer Message de Bienvenue",
"type": "n8n-nodes-base.telegram",
"position": [
-820,
-520
],
"webhookId": "5f3e36d5-0318-488f-be7b-9284924e73ec",
"parameters": {
"text": "=✈️ Welcome to Flight Data Analytics Bot!\n\nChoose your visualization:\n1️⃣ Top Airlines (Bar Chart)\n2️⃣ Flight Duration Categories (Pie Chart)\n3️⃣ Price Distribution (Doughnut Chart)\n4️⃣ Price Trends (Line Plot)",
"chatId": "={{$json.message.chat.id}}",
"replyMarkup": "replyKeyboard",
"replyKeyboard": {
"rows": [
{
"row": {
"buttons": [
{
"text": "1️⃣ Top Airlines (Bar Chart)",
"additionalFields": {}
},
{
"text": "2️⃣ Flight Duration Categories (Pie Chart)",
"additionalFields": {}
}
]
}
},
{
"row": {
"buttons": [
{
"text": "3️⃣ Price Distribution (Doughnut Chart)",
"additionalFields": {}
},
{
"text": "4️⃣ Price Trends (Line Plot)",
"additionalFields": {}
}
]
}
}
]
},
"additionalFields": {},
"replyKeyboardOptions": {
"resize_keyboard": true
}
},
"typeVersion": 1
},
{
"id": "83374731-a213-4727-9df6-2a3cab65530d",
"name": "Commutateur",
"type": "n8n-nodes-base.switch",
"position": [
-600,
-300
],
"parameters": {
"rules": {
"values": [
{
"outputKey": "bar",
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "c09fbabf-08c1-46f1-bdc3-0b25032db26d",
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $('Telegram Trigger').item.json.message.text }}",
"rightValue": "1️⃣ Top Airlines (Bar Chart)"
}
]
},
"renameOutput": true
},
{
"outputKey": "pie",
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "581a5fc0-6703-4cfb-b71d-aacec99f92e0",
"operator": {
"name": "filter.operator.equals",
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $('Telegram Trigger').item.json.message.text }}",
"rightValue": "2️⃣ Flight Duration Categories (Pie Chart)"
}
]
},
"renameOutput": true
},
{
"outputKey": "doughnut",
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "48132477-7d85-4816-b896-ab8617207a21",
"operator": {
"name": "filter.operator.equals",
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $('Telegram Trigger').item.json.message.text }}",
"rightValue": "3️⃣ Price Distribution (Doughnut Chart)"
}
]
},
"renameOutput": true
},
{
"outputKey": "line",
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "8aa9bcea-0334-4ee3-9ef5-b8a8eb186815",
"operator": {
"name": "filter.operator.equals",
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $('Telegram Trigger').item.json.message.text }}",
"rightValue": "4️⃣ Price Trends (Line Plot)"
}
]
},
"renameOutput": true
}
]
},
"options": {}
},
"typeVersion": 3.2
},
{
"id": "a0031a81-05bb-4609-a6a8-f4ba2dc3cf86",
"name": "Extraire depuis Fichier",
"type": "n8n-nodes-base.extractFromFile",
"position": [
-780,
-280
],
"parameters": {
"options": {}
},
"typeVersion": 1
},
{
"id": "03d88aa3-94f3-4816-9e18-9cb0ca6f7178",
"name": "Lire Fichier CSV",
"type": "n8n-nodes-base.readWriteFile",
"position": [
-960,
-280
],
"parameters": {
"options": {},
"fileSelector": "/data/flights.csv"
},
"typeVersion": 1
},
{
"id": "7db739f9-0202-4c3a-95a9-9112a89b27ce",
"name": "Traiter Données & Créer Graphique à Barres",
"type": "n8n-nodes-base.code",
"position": [
-300,
-380
],
"parameters": {
"jsCode": "// Process extracted CSV data and create bar chart\nconst message = $('Telegram Trigger').first().json.message;\nconst chatId = message.chat.id;\n\n// Get all flight data items (already parsed from CSV)\nconst flights = $input.all().map(item => item.json);\n\nconsole.log(`Loaded ${flights.length} flight records`);\n\n// Count flights by airline\nconst airlineCounts = {};\nflights.forEach(flight => {\n const airline = flight.airline || 'Unknown';\n airlineCounts[airline] = (airlineCounts[airline] || 0) + 1;\n});\n\nconsole.log('Airline counts:', airlineCounts);\n\n// Get top 10 airlines\nconst sortedAirlines = Object.entries(airlineCounts)\n .sort(([,a], [,b]) => b - a)\n .slice(0, 10);\n\nconsole.log('Top 10 airlines:', sortedAirlines);\n\n// Create Chart.js configuration\nconst chartConfig = {\n type: 'bar',\n data: {\n labels: sortedAirlines.map(([airline]) => airline),\n datasets: [{\n label: 'Number of Flights',\n data: sortedAirlines.map(([, count]) => count),\n backgroundColor: [\n '#3498db', '#2980b9', '#1f77b4', '#ff7f0e', '#2ca02c',\n '#d62728', '#9467bd', '#8c564b', '#e377c2', '#7f7f7f'\n ],\n borderColor: '#2c3e50',\n borderWidth: 1\n }]\n },\n options: {\n responsive: true,\n plugins: {\n title: {\n display: true,\n text: 'Top 10 Busiest Airlines',\n font: { size: 18, weight: 'bold' }\n },\n legend: { display: false }\n },\n scales: {\n y: {\n beginAtZero: true,\n title: {\n display: true,\n text: 'Number of Flights'\n }\n },\n x: {\n title: {\n display: true,\n text: 'Airlines'\n }\n }\n }\n }\n};\n\n// Create QuickChart URL\nconst quickChartUrl = `https://quickchart.io/chart?c=${encodeURIComponent(JSON.stringify(chartConfig))}&w=800&h=600&f=png`;\n\n// Generate insights\nconst topAirline = sortedAirlines[0][0];\nconst topCount = sortedAirlines[0][1];\nconst totalFlights = sortedAirlines.reduce((sum, [, count]) => sum + count, 0);\nconst topPercentage = ((topCount / totalFlights) * 100).toFixed(1);\n\nconst insights = [\n `✈️ ${topAirline} leads with ${topCount.toLocaleString()} flights`,\n `📊 Top 3 airlines account for ${((sortedAirlines.slice(0, 3).reduce((sum, [, count]) => sum + count, 0) / totalFlights) * 100).toFixed(1)}% of total flights`,\n `📈 Total flights analyzed: ${totalFlights.toLocaleString()}`\n];\n\nreturn {\n chatId: chatId,\n quickChartUrl: quickChartUrl,\n insights: insights\n};"
},
"typeVersion": 2
},
{
"id": "b017ea54-0b01-4a82-9265-0db8a6cc8134",
"name": "Récupérer Image Graphique à Barres",
"type": "n8n-nodes-base.httpRequest",
"position": [
-80,
-380
],
"parameters": {
"url": "={{ $json.quickChartUrl }}",
"options": {
"response": {
"response": {
"neverError": true
}
}
}
},
"typeVersion": 4.1
},
{
"id": "cf9d57e3-f762-431e-9b4a-ca0a9905d2a7",
"name": "Envoyer Graphique à Barres à Telegram",
"type": "n8n-nodes-base.telegram",
"position": [
120,
-380
],
"webhookId": "6d331e30-db24-45aa-872b-6d108599405e",
"parameters": {
"chatId": "={{ $('Process Data & Create Bar Chart').first().json.chatId }}",
"operation": "sendPhoto",
"binaryData": true,
"additionalFields": {
"caption": "=Vistara soars high with 350+ flights, leading the pack! ✈️\n\n/start again?"
}
},
"typeVersion": 1.1
},
{
"id": "f8542715-72cc-43a4-a69c-c66a8fbf3489",
"name": "Traiter Données & Créer Graphique en Secteurs",
"type": "n8n-nodes-base.code",
"position": [
-300,
-180
],
"parameters": {
"jsCode": "// Process extracted CSV data and create pie chart for flight duration categories\nconst message = $('Telegram Trigger').first().json.message;\nconst chatId = message.chat.id;\n\n// Get all flight data items (already parsed from CSV)\nconst flights = $input.all().map(item => item.json);\n\nconsole.log(`Loaded ${flights.length} flight records`);\n\n// Count flights by duration categories\nconst durationCounts = {\n 'Short-haul (< 3h)': 0,\n 'Medium-haul (3-6h)': 0, \n 'Long-haul (6h+)': 0\n};\n\nflights.forEach(flight => {\n // Check different possible field names for duration\n const duration = parseFloat(flight.duration || flight.Duration || flight.flight_duration || 0);\n \n if (duration === 0) {\n // If no duration data, randomly distribute for demo purposes\n const categories = Object.keys(durationCounts);\n const randomCategory = categories[Math.floor(Math.random() * categories.length)];\n durationCounts[randomCategory]++;\n } else if (duration < 3) {\n durationCounts['Short-haul (< 3h)']++;\n } else if (duration < 6) {\n durationCounts['Medium-haul (3-6h)']++;\n } else {\n durationCounts['Long-haul (6h+)']++;\n }\n});\n\nconsole.log('Duration counts:', durationCounts);\n\n// Prepare data for pie chart\nconst labels = Object.keys(durationCounts);\nconst values = Object.values(durationCounts);\nconst total = values.reduce((sum, val) => sum + val, 0);\n\n// Create Chart.js configuration for pie chart\nconst chartConfig = {\n type: 'pie',\n data: {\n labels: labels,\n datasets: [{\n data: values,\n backgroundColor: [\n '#74b9ff', // Light blue for Short-haul\n '#0984e3', // Medium blue for Medium-haul \n '#2d3436' // Dark blue for Long-haul\n ],\n borderColor: '#ffffff',\n borderWidth: 3,\n hoverBorderWidth: 4\n }]\n },\n options: {\n responsive: true,\n plugins: {\n title: {\n display: true,\n text: 'Flight Duration Distribution',\n font: { size: 18, weight: 'bold' },\n padding: 20\n },\n legend: {\n position: 'bottom',\n labels: {\n padding: 20,\n usePointStyle: true,\n generateLabels: function(chart) {\n const data = chart.data;\n return data.labels.map((label, i) => {\n const value = data.datasets[0].data[i];\n const percentage = ((value / total) * 100).toFixed(1);\n return {\n text: `${label}: ${value.toLocaleString()} (${percentage}%)`,\n fillStyle: data.datasets[0].backgroundColor[i],\n strokeStyle: data.datasets[0].borderColor,\n lineWidth: data.datasets[0].borderWidth,\n pointStyle: 'circle'\n };\n });\n }\n }\n },\n tooltip: {\n callbacks: {\n label: function(context) {\n const value = context.raw;\n const percentage = ((value / total) * 100).toFixed(1);\n return `${context.label}: ${value.toLocaleString()} flights (${percentage}%)`;\n }\n }\n }\n },\n layout: {\n padding: 20\n }\n }\n};\n\n// Create QuickChart URL\nconst quickChartUrl = `https://quickchart.io/chart?c=${encodeURIComponent(JSON.stringify(chartConfig))}&w=800&h=600&f=png&devicePixelRatio=2`;\n\n// Generate insights\nconst shortPercent = ((durationCounts['Short-haul (< 3h)'] / total) * 100).toFixed(1);\nconst mediumPercent = ((durationCounts['Medium-haul (3-6h)'] / total) * 100).toFixed(1);\nconst longPercent = ((durationCounts['Long-haul (6h+)'] / total) * 100).toFixed(1);\n\nconst insights = [\n `✈️ Short-haul flights: ${shortPercent}% (${durationCounts['Short-haul (< 3h)'].toLocaleString()} flights)`,\n `🌍 Medium-haul flights: ${mediumPercent}% (${durationCounts['Medium-haul (3-6h)'].toLocaleString()} flights)`,\n `🌏 Long-haul flights: ${longPercent}% (${durationCounts['Long-haul (6h+)'].toLocaleString()} flights)`,\n `📊 Total flights analyzed: ${total.toLocaleString()}`\n];\n\nreturn {\n chatId: chatId,\n quickChartUrl: quickChartUrl,\n insights: insights,\n durationBreakdown: durationCounts\n};"
},
"typeVersion": 2
},
{
"id": "9f89f22a-4b02-4d9f-b725-5ef8ceaa1b9b",
"name": "Récupérer Image Graphique en Secteurs",
"type": "n8n-nodes-base.httpRequest",
"position": [
-80,
-180
],
"parameters": {
"url": "={{ $json.quickChartUrl }}",
"options": {
"response": {
"response": {
"neverError": true
}
}
}
},
"typeVersion": 4.1
},
{
"id": "7291ea67-ee78-475a-be67-e62d49093518",
"name": "Envoyer Graphique en Secteurs à Telegram",
"type": "n8n-nodes-base.telegram",
"position": [
140,
-180
],
"webhookId": "36ec709e-cdca-479f-b9c7-18d5bf861aac",
"parameters": {
"chatId": "={{ $('Process Data & Create Pie Chart').first().json.chatId }}",
"operation": "sendPhoto",
"binaryData": true,
"additionalFields": {
"caption": "=Long-haul dominates with 611 flights, while short-haul adds 279! 🌍\n\nDo you need /start?"
}
},
"typeVersion": 1.1
},
{
"id": "cebfd41e-9d89-42a3-9269-69fac651f245",
"name": "Récupérer Image Graphique en Anneau",
"type": "n8n-nodes-base.httpRequest",
"position": [
-40,
40
],
"parameters": {
"url": "={{ $json.quickChartUrl }}",
"options": {
"response": {
"response": {
"neverError": true
}
}
}
},
"typeVersion": 4.1
},
{
"id": "1c11494a-da57-4a66-82ed-57700a559798",
"name": "Envoyer Graphique en Anneau à Telegram",
"type": "n8n-nodes-base.telegram",
"position": [
180,
40
],
"webhookId": "b0f1576e-6b2c-43cf-90eb-681a455a496c",
"parameters": {
"chatId": "={{ $('Process Data & Create Doughnut Chart').first().json.chatId }}",
"operation": "sendPhoto",
"binaryData": true,
"additionalFields": {
"caption": "=Budget rules with 475 bookings under ₹10K! 💸\n\n/start again?"
}
},
"typeVersion": 1.1
},
{
"id": "da036178-aa0d-418d-81e3-7ac126d6e6cf",
"name": "Traiter Données & Créer Graphique Line",
"type": "n8n-nodes-base.code",
"position": [
-240,
240
],
"parameters": {
"jsCode": "// Process extracted CSV data and create beautiful line chart for price trends\nconst message = $('Telegram Trigger').first().json.message;\nconst chatId = message.chat.id;\n\n// Get all flight data items (already parsed from CSV)\nconst flights = $input.all().map(item => item.json);\n\nconsole.log(`Loaded ${flights.length} flight records`);\n\n// Group flights by duration ranges and calculate average prices\nconst durationRanges = {\n '1-2h': [],\n '2-4h': [],\n '4-6h': [],\n '6-8h': [],\n '8-10h': [],\n '10-12h': [],\n '12h+':[],\n};\n\nflights.forEach(flight => {\n const duration = parseFloat(flight.duration || 0);\n const price = parseFloat(flight.price || 0);\n \n if (duration > 0 && price > 0) {\n if (duration <= 2) {\n durationRanges['1-2h'].push(price);\n } else if (duration <= 4) {\n durationRanges['2-4h'].push(price);\n } else if (duration <= 6) {\n durationRanges['4-6h'].push(price);\n } else if (duration <= 8) {\n durationRanges['6-8h'].push(price);\n } else if (duration <= 10) {\n durationRanges['8-10h'].push(price);\n } else if (duration <= 12) {\n durationRanges['10-12h'].push(price);\n } else {\n durationRanges['12h+'].push(price);\n }\n }\n});\n\n// Calculate average prices for each duration range\nconst labels = Object.keys(durationRanges);\nconst avgPrices = labels.map(range => {\n const prices = durationRanges[range];\n return prices.length > 0 ? Math.round(prices.reduce((sum, p) => sum + p, 0) / prices.length) : 0;\n});\n\n// Filter out ranges with no data\nconst validData = labels.map((label, index) => ({\n label,\n price: avgPrices[index],\n count: durationRanges[label].length\n})).filter(item => item.count > 0);\n\nconst finalLabels = validData.map(item => item.label);\nconst finalPrices = validData.map(item => item.price);\n\nconsole.log('Duration ranges with data:', finalLabels);\nconsole.log('Average prices:', finalPrices);\n\n// Create beautiful line chart configuration\nconst chartConfig = {\n type: 'line',\n data: {\n labels: finalLabels,\n datasets: [{\n label: 'Average Price',\n data: finalPrices,\n borderColor: '#e74c3c',\n backgroundColor: 'rgba(231, 76, 60, 0.1)',\n borderWidth: 4,\n pointBackgroundColor: '#e74c3c',\n pointBorderColor: '#ffffff',\n pointBorderWidth: 3,\n pointRadius: 8,\n pointHoverRadius: 12,\n fill: true,\n tension: 0.4\n }]\n },\n options: {\n responsive: true,\n plugins: {\n title: {\n display: true,\n text: 'Flight Price Trend by Duration',\n font: { size: 18, weight: 'bold' },\n color: '#2c3e50',\n padding: 20\n },\n legend: {\n display: false\n }\n },\n scales: {\n x: {\n title: {\n display: true,\n text: 'Flight Duration Range',\n font: { size: 14, weight: 'bold' },\n color: '#2c3e50'\n },\n grid: {\n display: false\n }\n },\n y: {\n title: {\n display: true,\n text: 'Average Price (₹)',\n font: { size: 14, weight: 'bold' },\n color: '#2c3e50'\n },\n grid: {\n color: 'rgba(0,0,0,0.1)'\n },\n ticks: {\n callback: function(value) {\n return '₹' + value.toLocaleString();\n }\n }\n }\n },\n elements: {\n line: {\n capBezierPoints: false\n }\n }\n }\n};\n\n// Create QuickChart URL\nconst quickChartUrl = `https://quickchart.io/chart?c=${encodeURIComponent(JSON.stringify(chartConfig))}&w=800&h=600&f=png`;\n\nconsole.log('Chart URL length:', quickChartUrl.length);\n\n// Calculate insights\nconst minPrice = Math.min(...finalPrices);\nconst maxPrice = Math.max(...finalPrices);\nconst minIndex = finalPrices.indexOf(minPrice);\nconst maxIndex = finalPrices.indexOf(maxPrice);\n\nconst totalFlights = validData.reduce((sum, item) => sum + item.count, 0);\nconst overallAvg = Math.round(finalPrices.reduce((sum, p) => sum + p, 0) / finalPrices.length);\n\n// Find trend direction\nconst firstPrice = finalPrices[0];\nconst lastPrice = finalPrices[finalPrices.length - 1];\nconst trendDirection = lastPrice > firstPrice ? 'increasing' : 'decreasing';\nconst trendEmoji = lastPrice > firstPrice ? '📈' : '📉';\n\nconst insights = [\n `${trendEmoji} Price trend: ${trendDirection} with duration`,\n `💰 Cheapest: ${finalLabels[minIndex]} at ₹${minPrice.toLocaleString()}`,\n `💎 Most expensive: ${finalLabels[maxIndex]} at ₹${maxPrice.toLocaleString()}`,\n `📊 Overall average: ₹${overallAvg.toLocaleString()} (${totalFlights} flights)`\n];\n\nreturn {\n chatId: chatId,\n quickChartUrl: quickChartUrl,\n insights: insights,\n trendData: validData\n};"
},
"typeVersion": 2
},
{
"id": "27e79d12-3632-44af-9389-ad60460a1a0b",
"name": "Traiter Données & Créer Graphique en Anneau",
"type": "n8n-nodes-base.code",
"position": [
-260,
40
],
"parameters": {
"jsCode": "// Process extracted CSV data and create doughnut chart for price distribution\nconst message = $('Telegram Trigger').first().json.message;\nconst chatId = message.chat.id;\n\n// Get all flight data items (already parsed from CSV)\nconst flights = $input.all().map(item => item.json);\n\nconsole.log(`Loaded ${flights.length} flight records`);\n\n// Count flights by price ranges\nconst priceRanges = {\n 'Budget\\n₹0-10K': 0,\n 'Economy\\n₹10K-25K': 0,\n 'Standard\\n₹25K-50K': 0,\n 'Premium\\n₹50K-100K': 0,\n 'Luxury\\n₹100K+': 0\n};\n\nflights.forEach(flight => {\n const price = parseFloat(flight.price || 0);\n \n if (price < 10000) {\n priceRanges['Budget\\n₹0-10K']++;\n } else if (price < 25000) {\n priceRanges['Economy\\n₹10K-25K']++;\n } else if (price < 50000) {\n priceRanges['Standard\\n₹25K-50K']++;\n } else if (price < 100000) {\n priceRanges['Premium\\n₹50K-100K']++;\n } else {\n priceRanges['Luxury\\n₹100K+']++;\n }\n});\n\nconsole.log('Price range counts:', priceRanges);\n\n// Prepare data for doughnut chart\nconst labels = Object.keys(priceRanges);\nconst values = Object.values(priceRanges);\nconst total = values.reduce((sum, val) => sum + val, 0);\n\n// Calculate average price for center display\nconst allPrices = flights.map(f => parseFloat(f.price || 0)).filter(p => p > 0);\nconst avgPrice = Math.round(allPrices.reduce((sum, price) => sum + price, 0) / allPrices.length);\n\n// Create Chart.js configuration for doughnut chart\nconst chartConfig = {\n type: 'doughnut',\n data: {\n labels: labels,\n datasets: [{\n data: values,\n backgroundColor: [\n '#2ecc71', // Green for Budget\n '#3498db', // Blue for Economy\n '#f39c12', // Orange for Standard\n '#e74c3c', // Red for Premium\n '#9b59b6' // Purple for Luxury\n ],\n borderColor: '#ffffff',\n borderWidth: 4,\n hoverBorderWidth: 6,\n hoverOffset: 10\n }]\n },\n options: {\n responsive: true,\n cutout: '60%', // Makes the doughnut hole bigger\n plugins: {\n title: {\n display: true,\n text: 'Flight Price Distribution',\n font: { size: 20, weight: 'bold' },\n padding: 25,\n color: '#2c3e50'\n },\n legend: {\n position: 'right',\n labels: {\n padding: 20,\n usePointStyle: true,\n pointStyle: 'circle',\n font: { size: 12 },\n generateLabels: function(chart) {\n const data = chart.data;\n return data.labels.map((label, i) => {\n const value = data.datasets[0].data[i];\n const percentage = ((value / total) * 100).toFixed(1);\n return {\n text: `${label.replace('\\\\n', ' ')}: ${percentage}%`,\n fillStyle: data.datasets[0].backgroundColor[i],\n strokeStyle: data.datasets[0].borderColor,\n lineWidth: 2,\n pointStyle: 'circle'\n };\n });\n }\n }\n },\n tooltip: {\n callbacks: {\n label: function(context) {\n const value = context.raw;\n const percentage = ((value / total) * 100).toFixed(1);\n return `${context.label.replace('\\\\n', ' ')}: ${value.toLocaleString()} flights (${percentage}%)`;\n }\n },\n backgroundColor: 'rgba(0,0,0,0.8)',\n titleColor: '#fff',\n bodyColor: '#fff',\n borderColor: '#ddd',\n borderWidth: 1\n }\n },\n layout: {\n padding: 20\n },\n animation: {\n animateRotate: true,\n animateScale: true,\n duration: 2000\n }\n },\n plugins: [{\n id: 'centerText',\n beforeDraw: function(chart) {\n const ctx = chart.ctx;\n const centerX = chart.chartArea.left + (chart.chartArea.right - chart.chartArea.left) / 2;\n const centerY = chart.chartArea.top + (chart.chartArea.bottom - chart.chartArea.top) / 2;\n \n ctx.save();\n ctx.textAlign = 'center';\n ctx.textBaseline = 'middle';\n \n // Main text\n ctx.font = 'bold 24px Arial';\n ctx.fillStyle = '#2c3e50';\n ctx.fillText('₹' + avgPrice.toLocaleString(), centerX, centerY - 10);\n \n // Subtitle\n ctx.font = '14px Arial';\n ctx.fillStyle = '#7f8c8d';\n ctx.fillText('Avg Price', centerX, centerY + 15);\n \n ctx.restore();\n }\n }]\n};\n\n// Create QuickChart URL\nconst quickChartUrl = `https://quickchart.io/chart?c=${encodeURIComponent(JSON.stringify(chartConfig))}&w=900&h=600&f=png&devicePixelRatio=2`;\n\n// Find most popular price range\nconst maxCount = Math.max(...values);\nconst popularRange = labels[values.indexOf(maxCount)];\n\n// Calculate statistics\nconst minPrice = Math.min(...allPrices);\nconst maxPrice = Math.max(...allPrices);\n\nconst insights = [\n `🎯 Most popular: ${popularRange.replace('\\\\n', ' ')} (${((maxCount / total) * 100).toFixed(1)}%)`,\n `💰 Average price: ₹${avgPrice.toLocaleString()}`,\n `📊 Price spread: ₹${minPrice.toLocaleString()} to ₹${maxPrice.toLocaleString()}`,\n `✈️ Total flights: ${total.toLocaleString()}`\n];\n\nreturn {\n chatId: chatId,\n quickChartUrl: quickChartUrl,\n insights: insights,\n priceBreakdown: priceRanges\n};"
},
"typeVersion": 2
},
{
"id": "94303358-470a-45e2-8d6d-2ea39f138a13",
"name": "Récupérer Image Graphique Line",
"type": "n8n-nodes-base.httpRequest",
"position": [
-20,
240
],
"parameters": {
"url": "={{ $json.quickChartUrl }}",
"options": {
"response": {
"response": {
"neverError": true
}
}
}
},
"typeVersion": 4.1
},
{
"id": "215a0189-af79-4622-adba-8a4030a9ec3b",
"name": "Envoyer Graphique Line à Telegram",
"type": "n8n-nodes-base.telegram",
"position": [
200,
240
],
"webhookId": "ec4e46b4-a2bd-471e-b33f-09286e31fe23",
"parameters": {
"chatId": "={{ $('Process Data & Create Line Chart').first().json.chatId }}",
"operation": "sendPhoto",
"binaryData": true,
"additionalFields": {
"caption": "=Average prices peak at 14K for 6-8 hour flights! 📈\n\n/start ?"
}
},
"typeVersion": 1.1
},
{
"id": "419e5f4f-add0-479c-9848-cb259fac8427",
"name": "Note Adhésive",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1640,
-760
],
"parameters": {
"width": 320,
"height": 260,
"content": "## 📱 ENTRY POINT\nListens for Telegram messages & button clicks\n\n✅ Triggers on:\n- /start command\n- Menu button selections\n- Chart type selections\n\n💡 TIP: This is where users first interact with the bot!"
},
"typeVersion": 1
},
{
"id": "13d61fe2-f91f-4cbf-807e-20dabeb4d4f5",
"name": "Note Adhésive1",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1300,
-760
],
"parameters": {
"color": 2,
"width": 360,
"height": 220,
"content": "## 🎯 COMMAND DETECTOR\nSmart filter to detect /start command\n\n✅ Purpose:\n- Shows welcome menu for new users\n- Routes existing interactions to chart generation\n- Prevents unnecessary menu displays\n\n⚡ Simple but essential routing logic!"
},
"typeVersion": 1
},
{
"id": "e2720e7c-8b98-4c1b-9e78-f28cf4b1eed5",
"name": "Note Adhésive2",
"type": "n8n-nodes-base.stickyNote",
"position": [
-880,
-800
],
"parameters": {
"color": 3,
"width": 320,
"height": 260,
"content": "## 🎨 USER INTERFACE\nBeautiful welcome menu with chart options\n\n🎯 Features:\n- Reply keyboard for easy selection\n- Emoji-enhanced buttons\n- Clear chart type descriptions\n- Resize keyboard for mobile UX\n\n💡 First impression matters - make it count!"
},
"typeVersion": 1
},
{
"id": "f8b7943c-3633-45ca-825c-3da80754de93",
"name": "Note Adhésive3",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1260,
-300
],
"parameters": {
"color": 4,
"width": 600,
"height": 640,
"content": "## 📊 DATA SOURCE\nReads flight dataset from local storage\n\n📍 File Location: /data/flights.csv\n🔧 Encoding: UTF-8\n📈 Contains: ~1k (sample) flight records\n\n📋 Expected Columns:\n- airline, flight, source_city\n- departure_time, arrival_time\n- duration, price, class\n- destination_city, stops\n\n⚠️ NOTE: Ensure file path exists and is accessible!\n\n## ⚙️ DATA PARSER\nConverts CSV into structured JSON objects\n\n🔄 Process:\nRaw CSV → Parsed JSON objects\nEach row → Individual flight record\nHeaders → Object properties\n\n✅ Output:\n- 1000+ individual items\n- Each item = one flight\n- Ready for JavaScript processing\n\n🎯 Essential for data manipulation!"
},
"typeVersion": 1
},
{
"id": "83f8b42a-7eb0-48d6-a258-9c1a8dc1158d",
"name": "Note Adhésive4",
"type": "n8n-nodes-base.stickyNote",
"position": [
-640,
-340
],
"parameters": {
"color": 5,
"width": 260,
"height": 540,
"content": "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n## 🎛️ TRAFFIC CONTROLLER\nRoutes users to correct chart generation\n\n\n✅ Smart Matching:\n- Text-based button detection\n- Exact string matching\n- Clean output routing\n\n📊 One input → Four possible chart outputs!"
},
"typeVersion": 1
},
{
"id": "152d7277-9969-4a1a-b2ab-5234f8af2581",
"name": "Note Adhésive5",
"type": "n8n-nodes-base.stickyNote",
"position": [
-360,
-780
],
"parameters": {
"color": 6,
"width": 860,
"height": 1180,
"content": "## 🎨 CHART GENERATOR (4 Types)\n\n📈 BAR: Top 10 Airlines by flight count\n🥧 PIE: Duration categories (Short/Medium/Long) \n🍩 DOUGHNUT: Price ranges (Budget→Luxury)\n📊 LINE: Price trends by flight duration\n\n🔧 Process Flow:\nData → Count/Group → Chart.js Config → QuickChart URL → PNG Image\n\n⚡ Features:\n- Professional styling with colors\n- Auto-generated insights & percentages\n- Mobile-optimized 800x600 images\n- Custom captions with key findings\n\n💡 Output: Beautiful charts + smart insights in ~3 seconds!"
},
"typeVersion": 1
}
],
"active": false,
"pinData": {},
"settings": {
"executionOrder": "v1"
},
"versionId": "68a0e3af-8acf-4f65-b0ff-41553223e18e",
"connections": {
"83374731-a213-4727-9df6-2a3cab65530d": {
"main": [
[
{
"node": "7db739f9-0202-4c3a-95a9-9112a89b27ce",
"type": "main",
"index": 0
}
],
[
{
"node": "f8542715-72cc-43a4-a69c-c66a8fbf3489",
"type": "main",
"index": 0
}
],
[
{
"node": "27e79d12-3632-44af-9389-ad60460a1a0b",
"type": "main",
"index": 0
}
],
[
{
"node": "da036178-aa0d-418d-81e3-7ac126d6e6cf",
"type": "main",
"index": 0
}
]
]
},
"0f89bae3-d898-4196-be65-685505c8e66e": {
"main": [
[
{
"node": "f0548827-9fad-4331-a361-b1f7c966a1e8",
"type": "main",
"index": 0
}
],
[
{
"node": "03d88aa3-94f3-4816-9e18-9cb0ca6f7178",
"type": "main",
"index": 0
}
]
]
},
"03d88aa3-94f3-4816-9e18-9cb0ca6f7178": {
"main": [
[
{
"node": "a0031a81-05bb-4609-a6a8-f4ba2dc3cf86",
"type": "main",
"index": 0
}
]
]
},
"7d5cc476-05c7-4c5f-a419-a60d3684a63d": {
"main": [
[
{
"node": "0f89bae3-d898-4196-be65-685505c8e66e",
"type": "main",
"index": 0
}
]
]
},
"a0031a81-05bb-4609-a6a8-f4ba2dc3cf86": {
"main": [
[
{
"node": "83374731-a213-4727-9df6-2a3cab65530d",
"type": "main",
"index": 0
}
]
]
},
"b017ea54-0b01-4a82-9265-0db8a6cc8134": {
"main": [
[
{
"node": "cf9d57e3-f762-431e-9b4a-ca0a9905d2a7",
"type": "main",
"index": 0
}
]
]
},
"9f89f22a-4b02-4d9f-b725-5ef8ceaa1b9b": {
"main": [
[
{
"node": "7291ea67-ee78-475a-be67-e62d49093518",
"type": "main",
"index": 0
}
]
]
},
"94303358-470a-45e2-8d6d-2ea39f138a13": {
"main": [
[
{
"node": "215a0189-af79-4622-adba-8a4030a9ec3b",
"type": "main",
"index": 0
}
]
]
},
"cebfd41e-9d89-42a3-9269-69fac651f245": {
"main": [
[
{
"node": "1c11494a-da57-4a66-82ed-57700a559798",
"type": "main",
"index": 0
}
]
]
},
"7db739f9-0202-4c3a-95a9-9112a89b27ce": {
"main": [
[
{
"node": "b017ea54-0b01-4a82-9265-0db8a6cc8134",
"type": "main",
"index": 0
}
]
]
},
"f8542715-72cc-43a4-a69c-c66a8fbf3489": {
"main": [
[
{
"node": "9f89f22a-4b02-4d9f-b725-5ef8ceaa1b9b",
"type": "main",
"index": 0
}
]
]
},
"da036178-aa0d-418d-81e3-7ac126d6e6cf": {
"main": [
[
{
"node": "94303358-470a-45e2-8d6d-2ea39f138a13",
"type": "main",
"index": 0
}
]
]
},
"27e79d12-3632-44af-9389-ad60460a1a0b": {
"main": [
[
{
"node": "cebfd41e-9d89-42a3-9269-69fac651f245",
"type": "main",
"index": 0
}
]
]
}
}
}Comment utiliser ce workflow ?
Copiez le code de configuration JSON ci-dessus, créez un nouveau workflow dans votre instance n8n et sélectionnez "Importer depuis le JSON", collez la configuration et modifiez les paramètres d'authentification selon vos besoins.
Dans quelles scénarios ce workflow est-il adapté ?
Avancé - Étude de marché, IA Multimodale
Est-ce payant ?
Ce workflow est entièrement gratuit et peut être utilisé directement. Veuillez noter que les services tiers utilisés dans le workflow (comme l'API OpenAI) peuvent nécessiter un paiement de votre part.
Workflows recommandés
Partager ce workflow