Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.
The reliable way to scrape values that appear on hover is to capture the chart library’s data or hover event—not to OCR a screenshot. Identify whether the page uses Plotly, Highcharts, or another library; read its point objects or event payload; and use real browser pointer movement only when no structured interface is exposed. This preserves raw values and works even when the chart is drawn on a canvas.
Start with the chart, not the tooltip
A tooltip is presentation output. It may round decimals, rename fields, combine several series, or disappear as soon as the pointer moves. The chart usually has a more stable source: a JavaScript data array, a point object, a network response, or a documented hover event.
- Open developer tools and inspect script names, DOM classes, global variables, and network requests.
- Look for library markers such as
js-plotly-plot,Plotly,Highcharts,highcharts-chart, or a chart-specific canvas element. - Prefer the library object or the request that supplied the data. Use rendered tooltip text only as a last resort.
- Record the chart state that matters: date range, filters, selected series, locale, timezone, and whether data loads after scrolling.
Do not bypass authentication, bot protection, rate limits, robots guidance, terms, or data-licensing restrictions. A chart that is visible to a visitor is not automatically licensed for redistribution.
PC Slower Than It Used to Be?
A free scan shows the junk files, broken settings and background clutter dragging Windows down - then fixes them in one click.Free scan · Windows 10 & 11Outdated Drivers Are Slowing You Down
One free scan finds every outdated or missing driver and matches the right update for your exact hardware.Free scan · exact hardware matchScrape Plotly hover values with plotly_hover
Plotly exposes a documented plotly_hover event on the graph div. The event contains a points array for the point under the cursor, along with fields such as the x and y values, series name, point number, and pixel coordinates. Registering this listener is normally more accurate than scraping a tooltip node.
#1 Best Overall
Capture every hovered point in the browser console
const gd = document.querySelector('.js-plotly-plot');
if (!gd) throw new Error('Plotly graph div not found');
gd.on('plotly_hover', (event) => {
const rows = event.points.map((p) => ({
curveNumber: p.curveNumber,
pointNumber: p.pointNumber,
series: p.data?.name ?? null,
x: p.x ?? null,
y: p.y ?? null,
text: p.text ?? null,
customdata: p.customdata ?? null,
bbox: p.bbox ?? null
}));
console.log(JSON.stringify(rows));
});
Move the pointer over a plotted mark. The logged object is the extraction record. Keep customdata when the page uses it for IDs or dimensions that are not displayed in the tooltip. The bbox information can help map an event back to a screen location when you are collecting observations with a browser driver.
Read Plotly’s loaded data without hovering
If the graph exposes its data array, you can serialize all loaded points directly. This avoids missing points that are too close together to target with a pointer.
const gd = document.querySelector('.js-plotly-plot');
const rows = gd.data.flatMap((trace, curveNumber) => {
const xs = trace.x || [];
const ys = trace.y || [];
return ys.map((y, pointNumber) => ({
curveNumber,
pointNumber,
series: trace.name ?? null,
x: xs[pointNumber] ?? pointNumber,
y,
text: trace.text?.[pointNumber] ?? trace.text ?? null,
customdata: trace.customdata?.[pointNumber] ?? null
}));
});
console.table(rows);
This method covers the data already present in the page. It will not magically include points held on the server but not yet requested, points removed by a filter, or values generated only after an interaction. Check network requests and chart controls before concluding that a series is incomplete.
The Tool Desk
Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →Outbyte PC Repair FREEClear out junk files and repair common Windows errorsFree Scan →Plotly details that change your output
- Several traces can share one x position. Depending on the hover mode, one event may contain multiple entries in
points. - Formatted labels can differ from raw values. Store
x,y, andcustomdatabefore using display text. - Dates and numbers may be JavaScript objects or strings. Normalize them with an explicit timezone and decimal policy in your pipeline.
- Responsive charts move their pixel bounding boxes after resize. Recalculate coordinates after viewport changes.
Extract Highcharts series and point objects
Highcharts associates tooltips with points in a series, and its mouse-tracking system controls point tooltips and click events. When the chart instance is exposed, reading chart.series and each series’ points is more stable than selecting tooltip markup.
Dump loaded Highcharts points
const chart = (window.Highcharts?.charts || []).find(Boolean);
if (!chart) throw new Error('Highcharts instance not found');
const rows = chart.series.flatMap((series, seriesIndex) =>
series.points.map((point, pointIndex) => ({
seriesIndex,
pointIndex,
series: series.name,
id: point.id ?? null,
x: point.x,
y: point.y,
category: point.category ?? null,
name: point.name ?? null,
custom: point.custom ?? null
}))
);
console.table(rows);
Some pages keep the chart instance in an application variable rather than Highcharts.charts. Inspect the page’s own initialization code or set a breakpoint where Highcharts.chart is called. A chart can also contain data grouped or reduced for performance; compare the point count with the visible range and the network response.
When the tooltip itself is the only exposed value
Highcharts normally renders an SVG tooltip. With the useHTML option, tooltip content can instead be HTML. After moving the pointer, inspect the resulting SVG or HTML nodes and read their text. Shared tooltips can return one value per series for a single x position; split tooltips can render separate labels; formatter functions can change both labels and ordering. Treat the tooltip as a view, not as a schema.
Rank #2
const tooltip = document.querySelector(
'.highcharts-tooltip, .highcharts-label, [class*="highcharts-tooltip"]'
);
console.log(tooltip?.textContent?.trim() || 'No visible tooltip');
Selectors vary by theme and version, so use this only after confirming the node in the inspector. If the chart is SVG, point elements may have coordinates but not their numeric values; the point object remains preferable.
Use a real browser when no data object is available
Canvas pixels do not expose point-level text in the DOM. If the site does not provide a usable chart object or data request, drive a browser, move the pointer over the chart, wait for a tooltip, and save each observation with its coordinates. The following Python example uses Playwright and scans a grid; adjust the URL and tooltip selectors after inspecting the page.
import asyncio
import json
from playwright.async_api import async_playwright
URL = "https://your-site.example/chart"
CHART = ".chart, canvas, .js-plotly-plot, .highcharts-container"
TOOLTIP_SELECTORS = [
".plotly-tooltip",
".highcharts-tooltip",
"[role='tooltip']",
".tooltip"
]
async def read_visible_tooltip(page):
for selector in TOOLTIP_SELECTORS:
locator = page.locator(selector).first
if await locator.is_visible():
text = (await locator.inner_text()).strip()
if text:
return text
return None
async def main():
async with async_playwright() as p:
browser = await p.chromium.launch(headless=True)
page = await browser.new_page(viewport={"width": 1440, "height": 1000})
await page.goto(URL, wait_until="networkidle", timeout=90000)
chart = page.locator(CHART).first
await chart.wait_for(state="visible", timeout=30000)
box = await chart.bounding_box()
if not box:
raise RuntimeError("Chart has no visible bounding box")
seen = {}
columns, rows = 80, 30
for row in range(rows):
y = box["y"] + box["height"] * (row + 0.5) / rows
for col in range(columns):
x = box["x"] + box["width"] * (col + 0.5) / columns
await page.mouse.move(x, y)
await page.wait_for_timeout(40)
text = await read_visible_tooltip(page)
if text:
key = (round(x, 1), round(y, 1), text)
seen[key] = {"x": x, "y": y, "text": text}
print(json.dumps(list(seen.values()), ensure_ascii=False, indent=2))
await browser.close()
asyncio.run(main())
Grid scanning is a fallback, not a guarantee that every point was hit. Dense lines, overlapping series, logarithmic axes, touch-oriented charts, and tooltips that require a precise marker can all produce misses. A library-specific routine that obtains point coordinates is more complete. Save the page URL, capture time, viewport, filters, and locale with the result so another run is comparable.
Make pointer collection less noisy
- Move slowly near likely marks and wait for the tooltip’s DOM node instead of sleeping a fixed interval.
- Scroll the chart into view and disable animations when the page supports it.
- Deduplicate by a stable key such as series plus x value, not by tooltip text alone.
- Capture a screenshot only for audit evidence; do not use OCR when the page exposes structured values.
Choose the method by stability and coverage
| Method | Stability | Coverage | Precision | Maintenance |
|---|---|---|---|---|
Plotly event or gd.data |
High while the page keeps its Plotly structure | SVG and canvas Plotly charts | Raw values and custom data | Low |
| Highcharts series and point objects | High when the chart instance is reachable | SVG and charts rendered with Highcharts | Raw point fields | Low to medium |
| Rendered SVG/HTML tooltip | Medium; themes and formatters change markup | Charts whose tooltip is in the DOM | Formatted labels, possibly rounded | Medium |
| Pointer automation on canvas | Lowest; coordinates and timing are sensitive | Any chart a real browser can interact with | Displayed tooltip text | High |
Use the highest row available. A successful screenshot does not prove that the value was captured; validation must compare extracted rows with visible axes, a second representation, or a sample of known points.
Validate, normalize, and store the results
Check that the tooltip belongs to the intended point
Revisit a sample of points and verify the series name, x label, y value, and coordinate. Shared tooltips can make a cursor appear to report one series while actually returning several. Keep the complete event or point record until validation is finished.
Quick wins for a faster PC:
Scan for outdated or missing drivers - takes under a minuteDriver Scan →Clear out junk files and repair common Windows errorsFree Scan →Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Preserve numeric meaning
Do not parse “1.2k” as 1.2. Prefer raw numeric fields; if only labels exist, store the original string and a separately documented normalized value. Record decimal separators, currency symbols, units, and the chart’s timezone.
Handle changing pages
For live charts, pin a capture timestamp and repeat the extraction under the same filters. For lazy-loaded charts, scroll or trigger the documented range control before reading the final series. If a network response contains the authoritative dataset, archive its request parameters and response version subject to the site’s rules.
Troubleshooting common failures
“No Plotly graph div found”
The chart may be inside an iframe, rendered after a delay, or use a different library. Wait for the frame, inspect all frames, and search for Plotly in loaded scripts. Do not assume a canvas means Plotly or that a class name is permanent.
The event fires but points is empty
You may be listening to the wrong element, hovering a non-data region, or using a chart configured without point interaction. Confirm that the listener is attached to the graph div and move over an actual mark. Check hover mode and trace visibility.
Highcharts is present but series is empty
Data may be loaded asynchronously, the selected range may contain no points, or the page may expose a wrapper rather than the chart instance. Wait for the series to populate and inspect network responses or the application’s chart reference.
The tooltip appears visually but selectors return nothing
It may be rendered in an iframe, shadow DOM, SVG text, or a transient portal. Inspect the tooltip while it is visible, query the correct frame or shadow root, and prefer the chart API if available.
Canvas scanning returns duplicates or misses points
Increase sampling near marks, wait for the tooltip to settle, and deduplicate by series and x value. For dense or overlapping charts, recover the underlying data request or library object; pixel coordinates alone cannot reconstruct hidden values.
Headless mode behaves differently
Use the same viewport, device scale, timezone, locale, authentication state, and consent choices as a normal visitor. Some sites defer data until a real interaction, so move the pointer and scroll deliberately rather than relying only on networkidle.
Or skip the browser setup: ScreenshotNeo
ScreenshotNeo is useful when you need a repeatable visual record of a chart rather than a custom scraper. It accepts consent banners before capture and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each step can be disabled. Only clean shots are billed: bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing, and the response identifies the result with X-Page-Verdict and X-Billed headers. It does not replace extracting Plotly or Highcharts point data, but it gives you a consistent image or PDF for checking what a visitor saw.
One GET request returns a PNG, JPEG, WebP, or PDF. See the ScreenshotNeo API documentation for all options, including waits, cookies, custom JavaScript, full-page capture, element selectors, and PDF settings.
cURL
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://your-site.example/chart -o chart.webp
Python
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://your-site.example/chart"}, timeout=90)
r.raise_for_status()
open("chart.webp", "wb").write(r.content)
Node.js
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://your-site.example/chart' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const fs = await import('node:fs/promises');
await fs.writeFile('chart.webp', Buffer.from(await res.arrayBuffer()));
ScreenshotNeo also provides an MCP server with take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients. The Free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 shots, and every feature is available on every plan. Create a free ScreenshotNeo account to try it.
Performance, reliability, and cost decisions
Reading a loaded chart object is usually faster and cheaper than launching a browser for every point. Reuse one browser context when automation is unavoidable, wait for a specific chart or tooltip selector, and cap retries for pages that repeatedly time out. Cache results only when the chart’s data freshness allows it. For large jobs, extract the underlying request once and process its structured response rather than moving a pointer over thousands of marks.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Keep failures separate from valid zero values and empty series. Log HTTP status, page verdict, authentication state, chart dimensions, and extraction method. A timeout or bot check is an operational failure, not a data point. If you use ScreenshotNeo for visual evidence, its response headers distinguish billed clean captures from non-billed failures and cache hits.
FAQ
Can I scrape a chart that has no visible tooltip?
Yes, if its JavaScript object or network response contains the points. A tooltip is optional presentation; it is not required for extraction.
Best Value
How do I keep a reproducible historical record?
Store the raw event or point objects, page state, capture time, timezone, viewport, and a visual capture for audit. Re-run a small validation sample whenever the publisher changes the chart.
What should I do when values are loaded only after login?
Use an authorized session and the site’s permitted API or browser flow. Do not evade access controls or share credentials with an extraction service.
Frequently Asked Questions
Can I scrape a chart that has no visible tooltip?
Yes. Read the chart’s JavaScript object or the network response that supplies its points; a tooltip is only a presentation layer.
How do I keep a reproducible historical record?
Save raw point or event data together with page state, capture time, timezone, viewport, and a validation screenshot.
What if the chart requires login?
Use an authorized session and any permitted API or browser flow. Do not bypass access controls or expose credentials.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.
Recommended Free Tools

