航班分析副本
高级
这是一个Market Research, Multimodal AI领域的自动化工作流,包含 24 个节点。主要使用 If, Code, Switch, Telegram, HttpRequest 等节点。 使用Chart.js、QuickChart API和Telegram机器人的航班数据可视化
前置要求
- •Telegram Bot Token
- •可能需要目标 API 的认证凭证
工作流预览
可视化展示节点连接关系,支持缩放和平移
导出工作流
复制以下 JSON 配置到 n8n 导入,即可使用此工作流
{
"id": "yQaIw7M18cPt7vGT",
"meta": {
"instanceId": "5cee0adb1ef2b84ac8a86937fac5115d710898b6c70f9f7c3f3ca3ef70a11bf7",
"templateCredsSetupCompleted": true
},
"name": "Flight_Analytics 副本",
"tags": [],
"nodes": [
{
"id": "7d5cc476-05c7-4c5f-a419-a60d3684a63d",
"name": "Telegram 触发器",
"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": "检查开始",
"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": "发送欢迎消息",
"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": "切换",
"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": "从文件提取",
"type": "n8n-nodes-base.extractFromFile",
"position": [
-780,
-280
],
"parameters": {
"options": {}
},
"typeVersion": 1
},
{
"id": "03d88aa3-94f3-4816-9e18-9cb0ca6f7178",
"name": "读取 CSV 文件",
"type": "n8n-nodes-base.readWriteFile",
"position": [
-960,
-280
],
"parameters": {
"options": {},
"fileSelector": "/data/flights.csv"
},
"typeVersion": 1
},
{
"id": "7db739f9-0202-4c3a-95a9-9112a89b27ce",
"name": "处理数据并创建条形图",
"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": "获取条形图图像",
"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": "发送条形图到 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": "处理数据并创建饼图",
"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": "获取饼图图像",
"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": "发送饼图到 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": "获取环形图图像",
"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": "发送环形图到 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": "处理数据并创建折线图",
"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": "处理数据并创建环形图",
"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": "获取折线图图像",
"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": "发送折线图到 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": "便签",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1640,
-760
],
"parameters": {
"width": 320,
"height": 260,
"content": "## 📱 入口点"
},
"typeVersion": 1
},
{
"id": "13d61fe2-f91f-4cbf-807e-20dabeb4d4f5",
"name": "便签1",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1300,
-760
],
"parameters": {
"color": 2,
"width": 360,
"height": 220,
"content": "## 🎯 命令检测器"
},
"typeVersion": 1
},
{
"id": "e2720e7c-8b98-4c1b-9e78-f28cf4b1eed5",
"name": "便签2",
"type": "n8n-nodes-base.stickyNote",
"position": [
-880,
-800
],
"parameters": {
"color": 3,
"width": 320,
"height": 260,
"content": "## 🎨 用户界面"
},
"typeVersion": 1
},
{
"id": "f8b7943c-3633-45ca-825c-3da80754de93",
"name": "便签3",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1260,
-300
],
"parameters": {
"color": 4,
"width": 600,
"height": 640,
"content": "## 📊 数据源"
},
"typeVersion": 1
},
{
"id": "83f8b42a-7eb0-48d6-a258-9c1a8dc1158d",
"name": "便签4",
"type": "n8n-nodes-base.stickyNote",
"position": [
-640,
-340
],
"parameters": {
"color": 5,
"width": 260,
"height": 540,
"content": ""
},
"typeVersion": 1
},
{
"id": "152d7277-9969-4a1a-b2ab-5234f8af2581",
"name": "便签5",
"type": "n8n-nodes-base.stickyNote",
"position": [
-360,
-780
],
"parameters": {
"color": 6,
"width": 860,
"height": 1180,
"content": "## 🎨 图表生成器(4 种类型)"
},
"typeVersion": 1
}
],
"active": false,
"pinData": {},
"settings": {
"executionOrder": "v1"
},
"versionId": "68a0e3af-8acf-4f65-b0ff-41553223e18e",
"connections": {
"Switch": {
"main": [
[
{
"node": "Process Data & Create Bar Chart",
"type": "main",
"index": 0
}
],
[
{
"node": "Process Data & Create Pie Chart",
"type": "main",
"index": 0
}
],
[
{
"node": "Process Data & Create Doughnut Chart",
"type": "main",
"index": 0
}
],
[
{
"node": "Process Data & Create Line Chart",
"type": "main",
"index": 0
}
]
]
},
"Check Start": {
"main": [
[
{
"node": "Send Welcome Message",
"type": "main",
"index": 0
}
],
[
{
"node": "Read CSV File",
"type": "main",
"index": 0
}
]
]
},
"Read CSV File": {
"main": [
[
{
"node": "Extract from File",
"type": "main",
"index": 0
}
]
]
},
"Telegram Trigger": {
"main": [
[
{
"node": "Check Start",
"type": "main",
"index": 0
}
]
]
},
"Extract from File": {
"main": [
[
{
"node": "Switch",
"type": "main",
"index": 0
}
]
]
},
"Fetch Bar Chart Image": {
"main": [
[
{
"node": "Send Bar Chart to Telegram",
"type": "main",
"index": 0
}
]
]
},
"Fetch Pie Chart Image": {
"main": [
[
{
"node": "Send Pie Chart to Telegram",
"type": "main",
"index": 0
}
]
]
},
"Fetch Line Chart Image": {
"main": [
[
{
"node": "Send Line Chart to Telegram",
"type": "main",
"index": 0
}
]
]
},
"Fetch Doughnut Chart Image": {
"main": [
[
{
"node": "Send Doughnut Chart to Telegram",
"type": "main",
"index": 0
}
]
]
},
"Process Data & Create Bar Chart": {
"main": [
[
{
"node": "Fetch Bar Chart Image",
"type": "main",
"index": 0
}
]
]
},
"Process Data & Create Pie Chart": {
"main": [
[
{
"node": "Fetch Pie Chart Image",
"type": "main",
"index": 0
}
]
]
},
"Process Data & Create Line Chart": {
"main": [
[
{
"node": "Fetch Line Chart Image",
"type": "main",
"index": 0
}
]
]
},
"Process Data & Create Doughnut Chart": {
"main": [
[
{
"node": "Fetch Doughnut Chart Image",
"type": "main",
"index": 0
}
]
]
}
}
}常见问题
如何使用这个工作流?
复制上方的 JSON 配置代码,在您的 n8n 实例中创建新工作流并选择「从 JSON 导入」,粘贴配置后根据需要修改凭证设置即可。
这个工作流适合什么场景?
高级 - 市场调研, 多模态 AI
需要付费吗?
本工作流完全免费,您可以直接导入使用。但请注意,工作流中使用的第三方服务(如 OpenAI API)可能需要您自行付费。
相关工作流推荐
自动发布到 WooCommerce_模板
自动将Telegram频道帖子转换为WooCommerce产品
If
Set
Code
+9
26 节点Shohani
内容创作
美甲沙龙:主代理V2 Telegram版
集成Telegram、Claude和GPT5-mini的多智能体沙龙预约管理系统
If
Set
Code
+19
67 节点Denis
内容创作
Nano Banana-Gemini 2.5多模态Telegram机器人
基于Nano Banana/Gemini 2.5的多模态Telegram机器人
If
Set
Code
+11
36 节点Denis
内容创作
家居装饰AI(Google Nano Banana)- Santhej Kallada
基于Google Gemini的AI图像生成与编辑及Telegram机器人
If
Set
Code
+9
28 节点Santhej Kallada
内容创作
✨🩷自动化社交媒体内容发布工厂 + 系统提示组合
基于动态系统提示和GPT-4o的AI驱动多平台社交媒体内容工厂
If
Set
Code
+20
100 节点Amit Mehta
内容创作
LinkedIn和X病毒内容自动引擎
使用AI生成和发布自动创建LinkedIn和X的病毒内容
If
Set
Wait
+26
156 节点Diptamoy Barman
内容创作