AutomationFlowsSlack & Telegram › Flight Data Visualization with Chart.js, Quickchart API & Telegram Bot

Flight Data Visualization with Chart.js, Quickchart API & Telegram Bot

ByDataMinex @dataminex on n8n.io

This advanced n8n workflow creates an intelligent Telegram bot that transforms raw CSV flight data into stunning, interactive visualizations. Users can generate professional charts on-demand through a conversational interface, making data analytics accessible to anyone via…

Event trigger★★★★☆ complexity24 nodesTelegram TriggerTelegramRead Write FileHTTP Request
Slack & Telegram Trigger: Event Nodes: 24 Complexity: ★★★★☆ Added:

This workflow corresponds to n8n.io template #7238 — we link there as the canonical source.

This workflow follows the HTTP Request → Readwritefile recipe pattern — see all workflows that pair these two integrations.

The workflow JSON

Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →

Download .json
{
  "id": "yQaIw7M18cPt7vGT",
  "meta": {
    "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
      ],
      "parameters": {
        "updates": [
          "message",
          "callback_query"
        ],
        "additionalFields": {}
      },
      "typeVersion": 1.1
    },
    {
      "id": "0f89bae3-d898-4196-be65-685505c8e66e",
      "name": "Check Start",
      "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": "Send Welcome Message",
      "type": "n8n-nodes-base.telegram",
      "position": [
        -820,
        -520
      ],
      "parameters": {
        "text": "=\u2708\ufe0f Welcome to Flight Data Analytics Bot!\n\nChoose your visualization:\n1\ufe0f\u20e3 Top Airlines (Bar Chart)\n2\ufe0f\u20e3 Flight Duration Categories (Pie Chart)\n3\ufe0f\u20e3 Price Distribution (Doughnut Chart)\n4\ufe0f\u20e3 Price Trends (Line Plot)",
        "chatId": "={{$json.message.chat.id}}",
        "replyMarkup": "replyKeyboard",
        "replyKeyboard": {
          "rows": [
            {
              "row": {
                "buttons": [
                  {
                    "text": "1\ufe0f\u20e3 Top Airlines (Bar Chart)",
                    "additionalFields": {}
                  },
                  {
                    "text": "2\ufe0f\u20e3 Flight Duration Categories (Pie Chart)",
                    "additionalFields": {}
                  }
                ]
              }
            },
            {
              "row": {
                "buttons": [
                  {
                    "text": "3\ufe0f\u20e3 Price Distribution (Doughnut Chart)",
                    "additionalFields": {}
                  },
                  {
                    "text": "4\ufe0f\u20e3 Price Trends (Line Plot)",
                    "additionalFields": {}
                  }
                ]
              }
            }
          ]
        },
        "additionalFields": {},
        "replyKeyboardOptions": {
          "resize_keyboard": true
        }
      },
      "typeVersion": 1
    },
    {
      "id": "83374731-a213-4727-9df6-2a3cab65530d",
      "name": "Switch",
      "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\ufe0f\u20e3 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\ufe0f\u20e3 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\ufe0f\u20e3 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\ufe0f\u20e3 Price Trends (Line Plot)"
                  }
                ]
              },
              "renameOutput": true
            }
          ]
        },
        "options": {}
      },
      "typeVersion": 3.2
    },
    {
      "id": "a0031a81-05bb-4609-a6a8-f4ba2dc3cf86",
      "name": "Extract from File",
      "type": "n8n-nodes-base.extractFromFile",
      "position": [
        -780,
        -280
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 1
    },
    {
      "id": "03d88aa3-94f3-4816-9e18-9cb0ca6f7178",
      "name": "Read CSV File",
      "type": "n8n-nodes-base.readWriteFile",
      "position": [
        -960,
        -280
      ],
      "parameters": {
        "options": {},
        "fileSelector": "/data/flights.csv"
      },
      "typeVersion": 1
    },
    {
      "id": "7db739f9-0202-4c3a-95a9-9112a89b27ce",
      "name": "Process Data & Create Bar Chart",
      "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  `\u2708\ufe0f ${topAirline} leads with ${topCount.toLocaleString()} flights`,\n  `\ud83d\udcca Top 3 airlines account for ${((sortedAirlines.slice(0, 3).reduce((sum, [, count]) => sum + count, 0) / totalFlights) * 100).toFixed(1)}% of total flights`,\n  `\ud83d\udcc8 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": "Fetch Bar Chart Image",
      "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": "Send Bar Chart to Telegram",
      "type": "n8n-nodes-base.telegram",
      "position": [
        120,
        -380
      ],
      "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! \u2708\ufe0f\n\n/start again?"
        }
      },
      "typeVersion": 1.1
    },
    {
      "id": "f8542715-72cc-43a4-a69c-c66a8fbf3489",
      "name": "Process Data & Create Pie Chart",
      "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  `\u2708\ufe0f Short-haul flights: ${shortPercent}% (${durationCounts['Short-haul (< 3h)'].toLocaleString()} flights)`,\n  `\ud83c\udf0d Medium-haul flights: ${mediumPercent}% (${durationCounts['Medium-haul (3-6h)'].toLocaleString()} flights)`,\n  `\ud83c\udf0f Long-haul flights: ${longPercent}% (${durationCounts['Long-haul (6h+)'].toLocaleString()} flights)`,\n  `\ud83d\udcca 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": "Fetch Pie Chart Image",
      "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": "Send Pie Chart to Telegram",
      "type": "n8n-nodes-base.telegram",
      "position": [
        140,
        -180
      ],
      "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! \ud83c\udf0d\n\nDo you need /start?"
        }
      },
      "typeVersion": 1.1
    },
    {
      "id": "cebfd41e-9d89-42a3-9269-69fac651f245",
      "name": "Fetch Doughnut Chart Image",
      "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": "Send Doughnut Chart to Telegram",
      "type": "n8n-nodes-base.telegram",
      "position": [
        180,
        40
      ],
      "parameters": {
        "chatId": "={{ $('Process Data & Create Doughnut Chart').first().json.chatId }}",
        "operation": "sendPhoto",
        "binaryData": true,
        "additionalFields": {
          "caption": "=Budget rules with 475 bookings under \u20b910K! \ud83d\udcb8\n\n/start again?"
        }
      },
      "typeVersion": 1.1
    },
    {
      "id": "da036178-aa0d-418d-81e3-7ac126d6e6cf",
      "name": "Process Data & Create Line Chart",
      "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 (\u20b9)',\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 '\u20b9' + 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 ? '\ud83d\udcc8' : '\ud83d\udcc9';\n\nconst insights = [\n  `${trendEmoji} Price trend: ${trendDirection} with duration`,\n  `\ud83d\udcb0 Cheapest: ${finalLabels[minIndex]} at \u20b9${minPrice.toLocaleString()}`,\n  `\ud83d\udc8e Most expensive: ${finalLabels[maxIndex]} at \u20b9${maxPrice.toLocaleString()}`,\n  `\ud83d\udcca Overall average: \u20b9${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": "Process Data & Create Doughnut Chart",
      "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\u20b90-10K': 0,\n  'Economy\\n\u20b910K-25K': 0,\n  'Standard\\n\u20b925K-50K': 0,\n  'Premium\\n\u20b950K-100K': 0,\n  'Luxury\\n\u20b9100K+': 0\n};\n\nflights.forEach(flight => {\n  const price = parseFloat(flight.price || 0);\n  \n  if (price < 10000) {\n    priceRanges['Budget\\n\u20b90-10K']++;\n  } else if (price < 25000) {\n    priceRanges['Economy\\n\u20b910K-25K']++;\n  } else if (price < 50000) {\n    priceRanges['Standard\\n\u20b925K-50K']++;\n  } else if (price < 100000) {\n    priceRanges['Premium\\n\u20b950K-100K']++;\n  } else {\n    priceRanges['Luxury\\n\u20b9100K+']++;\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('\u20b9' + 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  `\ud83c\udfaf Most popular: ${popularRange.replace('\\\\n', ' ')} (${((maxCount / total) * 100).toFixed(1)}%)`,\n  `\ud83d\udcb0 Average price: \u20b9${avgPrice.toLocaleString()}`,\n  `\ud83d\udcca Price spread: \u20b9${minPrice.toLocaleString()} to \u20b9${maxPrice.toLocaleString()}`,\n  `\u2708\ufe0f 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": "Fetch Line Chart Image",
      "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": "Send Line Chart to Telegram",
      "type": "n8n-nodes-base.telegram",
      "position": [
        200,
        240
      ],
      "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! \ud83d\udcc8\n\n/start ?"
        }
      },
      "typeVersion": 1.1
    },
    {
      "id": "419e5f4f-add0-479c-9848-cb259fac8427",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1640,
        -760
      ],
      "parameters": {
        "width": 320,
        "height": 260,
        "content": "## \ud83d\udcf1 ENTRY POINT\nListens for Telegram messages & button clicks\n\n\u2705 Triggers on:\n- /start command\n- Menu button selections\n- Chart type selections\n\n\ud83d\udca1 TIP: This is where users first interact with the bot!"
      },
      "typeVersion": 1
    },
    {
      "id": "13d61fe2-f91f-4cbf-807e-20dabeb4d4f5",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1300,
        -760
      ],
      "parameters": {
        "color": 2,
        "width": 360,
        "height": 220,
        "content": "## \ud83c\udfaf COMMAND DETECTOR\nSmart filter to detect /start command\n\n\u2705 Purpose:\n- Shows welcome menu for new users\n- Routes existing interactions to chart generation\n- Prevents unnecessary menu displays\n\n\u26a1 Simple but essential routing logic!"
      },
      "typeVersion": 1
    },
    {
      "id": "e2720e7c-8b98-4c1b-9e78-f28cf4b1eed5",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -880,
        -800
      ],
      "parameters": {
        "color": 3,
        "width": 320,
        "height": 260,
        "content": "## \ud83c\udfa8 USER INTERFACE\nBeautiful welcome menu with chart options\n\n\ud83c\udfaf Features:\n- Reply keyboard for easy selection\n- Emoji-enhanced buttons\n- Clear chart type descriptions\n- Resize keyboard for mobile UX\n\n\ud83d\udca1 First impression matters - make it count!"
      },
      "typeVersion": 1
    },
    {
      "id": "f8b7943c-3633-45ca-825c-3da80754de93",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1260,
        -300
      ],
      "parameters": {
        "color": 4,
        "width": 600,
        "height": 640,
        "content": "## \ud83d\udcca DATA SOURCE\nReads flight dataset from local storage\n\n\ud83d\udccd File Location: /data/flights.csv\n\ud83d\udd27 Encoding: UTF-8\n\ud83d\udcc8 Contains: ~1k (sample) flight records\n\n\ud83d\udccb Expected Columns:\n- airline, flight, source_city\n- departure_time, arrival_time\n- duration, price, class\n- destination_city, stops\n\n\u26a0\ufe0f NOTE: Ensure file path exists and is accessible!\n\n## \u2699\ufe0f DATA PARSER\nConverts CSV into structured JSON objects\n\n\ud83d\udd04 Process:\nRaw CSV \u2192 Parsed JSON objects\nEach row \u2192 Individual flight record\nHeaders \u2192 Object properties\n\n\u2705 Output:\n- 1000+ individual items\n- Each item = one flight\n- Ready for JavaScript processing\n\n\ud83c\udfaf Essential for data manipulation!"
      },
      "typeVersion": 1
    },
    {
      "id": "83f8b42a-7eb0-48d6-a258-9c1a8dc1158d",
      "name": "Sticky Note4",
      "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## \ud83c\udf9b\ufe0f TRAFFIC CONTROLLER\nRoutes users to correct chart generation\n\n\n\u2705 Smart Matching:\n- Text-based button detection\n- Exact string matching\n- Clean output routing\n\n\ud83d\udcca One input \u2192 Four possible chart outputs!"
      },
      "typeVersion": 1
    },
    {
      "id": "152d7277-9969-4a1a-b2ab-5234f8af2581",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -360,
        -780
      ],
      "parameters": {
        "color": 6,
        "width": 860,
        "height": 1180,
        "content": "## \ud83c\udfa8 CHART GENERATOR (4 Types)\n\n\ud83d\udcc8 BAR: Top 10 Airlines by flight count\n\ud83e\udd67 PIE: Duration categories (Short/Medium/Long)  \n\ud83c\udf69 DOUGHNUT: Price ranges (Budget\u2192Luxury)\n\ud83d\udcca LINE: Price trends by flight duration\n\n\ud83d\udd27 Process Flow:\nData \u2192 Count/Group \u2192 Chart.js Config \u2192 QuickChart URL \u2192 PNG Image\n\n\u26a1 Features:\n- Professional styling with colors\n- Auto-generated insights & percentages\n- Mobile-optimized 800x600 images\n- Custom captions with key findings\n\n\ud83d\udca1 Output: Beautiful charts + smart insights in ~3 seconds!"
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "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
          }
        ]
      ]
    }
  }
}
Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

About this workflow

This advanced n8n workflow creates an intelligent Telegram bot that transforms raw CSV flight data into stunning, interactive visualizations. Users can generate professional charts on-demand through a conversational interface, making data analytics accessible to anyone via…

Source: https://n8n.io/workflows/7238/ — original creator credit. Request a take-down →

More Slack & Telegram workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

Slack & Telegram

This n8n template automatically converts Telegram channel posts into WooCommerce products, perfect for businesses that share products on Telegram and want to sync them with their online shop.

Telegram Trigger, WooCommerce, Telegram +2
Slack & Telegram

TextMain. Uses telegramTrigger, stopAndError, telegram, httpRequest. Event-driven trigger; 56 nodes.

Telegram Trigger, Stop And Error, Telegram +2
Slack & Telegram

Pede Ai. Uses httpRequest, telegram, postgres, telegramTrigger. Event-driven trigger; 53 nodes.

HTTP Request, Telegram, Postgres +1
Slack & Telegram

📄 Documentation: Notion Guide

Telegram Trigger, @Blotato/N8N Nodes Blotato, Telegram +1
Slack & Telegram

Telegram Wait. Uses stickyNote, httpRequest, redis, noOp. Event-driven trigger; 36 nodes.

HTTP Request, Redis, Telegram +1