Overview
The /chart command generates a visual line chart showing the complete price history of a tracked product. The chart is created using Chart.js and rendered as an image using Playwright.
Syntax
The Amazon product URL of the tracked item
How It Works
- Product Lookup - Finds the product by sanitized URL
- History Validation - Checks that at least 2 data points exist
- Data Preparation - Sorts price history chronologically
- Chart Generation - Renders Chart.js visualization in headless browser
- Screenshot - Captures the canvas as an image buffer
- Delivery - Sends the chart as a photo with product details
Chart generation takes 3-5 seconds as it launches a browser to render the visualization.
Examples
Basic Chart Request
/chart https://www.amazon.com/dp/B08N5WRWNW
Bot Response:
A photo showing a line chart with:
- X-axis: Dates (formatted as “10 Mar, 14:30”)
- Y-axis: Prices in USD
- Title: Product name
- Line: Price trend over time
Caption:
📊 Histórico de precios
Apple AirPods Pro (2nd Generation)
[🛒 Ver en Amazon]
Interactive Chart via /list
- Run
/list
- Click on a product
- Click 📈 Ver gráfico
The chart is automatically generated for the selected product.
Chart Specifications
Visual Properties
Chart type (line chart for time series)
Line smoothing (0.2 = slightly curved)
No area fill under the line
{
labels: ["01 Mar, 10:00", "02 Mar, 14:30", "03 Mar, 18:15", ...],
datasets: [{
label: "Precio",
data: [249.99, 239.99, 229.99, ...],
borderWidth: 2,
tension: 0.2,
fill: false
}]
}
Error Handling
Product Not Found
/chart https://www.amazon.com/dp/INVALID123
Response:
⚠️ No encontré ese producto en seguimiento. Usa /list
Insufficient History
/chart https://www.amazon.com/dp/B08N5WRWNW
Response:
📉 No hay suficiente historial para graficar (se requieren al menos 2 registros).
A product must have at least 2 price history entries to generate a chart. Newly added products will only have 1 entry initially.
Chart Generation Failed
Response:
❌ Ocurrió un error generando el gráfico.
Common causes:
- Browser launch failure
- Memory issues
- Network problems loading Chart.js CDN
Code Implementation
The /chart command is implemented in index.mjs:613-623:
bot.onText(/\/chart (.+)/, async (msg, match) => {
const chatId = msg.chat.id;
const raw = match[1].trim();
const key = sanitizeAmazonURL(raw);
if (!priceData[key]) {
return bot.sendMessage(chatId, "⚠️ No encontré ese producto en seguimiento. Usa /list", { parse_mode: "Markdown" });
}
await sendPriceChart(chatId, key);
});
Chart Generation Function
The sendPriceChart() function (lines 309-333) handles the chart creation:
async function sendPriceChart(chatId, productUrl) {
const product = priceData[productUrl];
if (!product) return bot.sendMessage(chatId, "⚠️ Producto no encontrado en seguimiento. Usa /list");
if (!Array.isArray(product.history) || product.history.length < 2) {
return bot.sendMessage(chatId, "📉 No hay suficiente historial para graficar (se requieren al menos 2 registros).");
}
const sorted = product.history.map(h => ({ date: new Date(h.date), price: h.price })).sort((a, b) => a.date - b.date);
const labels = sorted.map(s => s.date.toLocaleString("es-MX", { day: "2-digit", month: "short", hour: "2-digit", minute: "2-digit" }));
const prices = sorted.map(s => s.price);
try {
const buf = await generateChartBuffer(labels, prices, product.title);
await bot.sendPhoto(chatId, buf, {
caption: `📊 *Histórico de precios*\n${escapeMD(product.title)}`,
parse_mode: "Markdown",
reply_markup: { inline_keyboard: [[{ text: "🛒 Ver en Amazon", url: productUrl }]] }
});
} catch (err) {
console.error("❌ Error generando gráfico:", err.message);
await bot.sendMessage(chatId, "❌ Ocurrió un error generando el gráfico.");
}
}
Chart Rendering with Playwright
The generateChartBuffer() function (lines 253-307) uses Playwright to render the chart:
async function generateChartBuffer(labels, prices, title) {
const browser = await chromium.launch({ headless: true });
const ctx = await browser.newContext();
const page = await ctx.newPage();
const html = `
<html>
<head>
<meta charset="utf-8" />
<style>body{margin:0;padding:0}</style>
</head>
<body>
<canvas id="chart" width="900" height="420"></canvas>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
const labels = ${JSON.stringify(labels)};
const data = ${JSON.stringify(prices)};
const title = ${JSON.stringify(title || "")};
const ctx = document.getElementById('chart').getContext('2d');
new Chart(ctx, {
type: 'line',
data: {
labels,
datasets: [{
label: 'Precio',
data,
borderWidth: 2,
tension: 0.2,
fill: false
}]
},
options: {
responsive: false,
plugins: {
title: { display: true, text: title }
},
scales: {
y: { beginAtZero: false }
}
}
});
</script>
</body>
</html>
`;
await page.setContent(html, { waitUntil: "load" });
await page.waitForTimeout(1000);
const canvas = await page.$("#chart");
const buffer = await canvas.screenshot();
await browser.close();
return buffer;
}
Process Breakdown
- Launch Browser - Starts headless Chromium
- Create HTML - Injects Chart.js from CDN
- Pass Data - Serializes labels and prices into JavaScript
- Render Chart - Chart.js draws the visualization
- Wait - 1 second delay for rendering to complete
- Screenshot - Captures the canvas element
- Return Buffer - Returns image as binary buffer
- Cleanup - Closes browser
The chart uses Chart.js version from the CDN. If the CDN is down, chart generation will fail.
Dates on the X-axis are formatted using Spanish locale:
const labels = sorted.map(s =>
s.date.toLocaleString("es-MX", {
day: "2-digit",
month: "short",
hour: "2-digit",
minute: "2-digit"
})
);
Example Output:
"01 mar, 10:30"
"15 mar, 14:45"
"28 mar, 09:15"
Price History Data
Price history is stored in the product object:
{
"history": [
{ "date": "2026-03-01T10:30:00.000Z", "price": 249.99 },
{ "date": "2026-03-01T12:30:00.000Z", "price": 249.99 },
{ "date": "2026-03-01T14:30:00.000Z", "price": 239.99 },
{ "date": "2026-03-01T16:30:00.000Z", "price": 229.99 }
]
}
History is automatically updated every time prices are checked (every 2 hours or via /check).
History Limit
The bot maintains up to 120 price history entries per product:
const HISTORY_LIMIT = 120;
// In checkPrices() function:
updated.history.push({ date: new Date().toISOString(), price: scraped.price });
if (updated.history.length > HISTORY_LIMIT) {
updated.history = updated.history.slice(-HISTORY_LIMIT);
}
Coverage Estimate:
- Checks every 2 hours = 12 checks per day
- 120 entries ÷ 12 = ~10 days of history
Older than 10 days of data will be automatically removed to save storage space.
Use Cases
Identify Trends
Visualize price patterns:
/chart https://www.amazon.com/dp/B08N5WRWNW
Determine if prices are generally rising, falling, or stable.
Optimal Purchase Timing
See historical lows:
/chart https://www.amazon.com/dp/B08N5WRWNW
Decide if the current price is near the historical minimum.
Validate Tracking
Confirm data is being collected:
/add https://www.amazon.com/...
/check
/chart https://www.amazon.com/...
Verify that multiple data points are accumulating.
Share with Others
Export the chart image:
- Run
/chart [url]
- Download the image from Telegram
- Share with friends/family
Chart Customization
While the chart is not customizable via commands, you can modify the code:
Change Colors
datasets: [{
label: 'Precio',
data,
borderColor: 'rgb(75, 192, 192)', // Add this
backgroundColor: 'rgba(75, 192, 192, 0.2)', // Add this
borderWidth: 2,
tension: 0.2,
fill: true // Change to true for filled area
}]
Add Grid Lines
scales: {
y: {
beginAtZero: false,
grid: { display: true } // Add this
},
x: {
grid: { display: true } // Add this
}
}
Change Chart Size
<canvas id="chart" width="1200" height="600"></canvas>
Generation Time
| Factor | Impact |
|---|
| Browser launch | ~1-2 seconds |
| Page load + Chart.js | ~1 second |
| Rendering wait | 1 second |
| Screenshot | ~0.5 seconds |
| Total | ~3-5 seconds |
Memory Usage
Each chart generation:
- Launches a full Chromium instance
- Loads Chart.js library (~200KB)
- Renders HTML canvas
- Creates image buffer (~50-200KB)
For multiple charts, wait a few seconds between requests to avoid memory issues.
Best Practices
- Wait for History - Add products and wait 24-48 hours before charting
- Check Before Charting - Run
/check to ensure latest data is included
- Use /list Shortcuts - Click the chart button in product details
- Compare with Lowest Price - Use
/list to see if current price is near the low
- Screenshot for Records - Save chart images to track long-term trends
- /add - Add products to start collecting history
- /check - Update prices before generating charts
- /list - Access chart button in product details
- /stats - View aggregate statistics
- /edit - Update URLs while preserving history