Visualizing quadrature functions as point clouds#
Author: Henrik N.T. Finsberg
SPDX-License-Identifier: MIT
Quadrature functions are not possible to visualize directly in ParaView, as they are not defined on a mesh.
However, we can visualize them as point clouds.
In this example we will show how you can use scifem to save your quadrature fuctions as XDMF files,
which can be loaded into ParaView for visualization.
Note
This demo requires a backend for writing HDF5 files. Please install scifem with either scifem[adios2] or scifem[h5py].
First, we import the necessary modules.
# First initialize logging
logging.basicConfig(level=logging.INFO)
Now let’s create a quadrature function on a unit square mesh. We will use the basix.ufl module to create the quadrature element.
mesh = dolfinx.mesh.create_unit_square(MPI.COMM_WORLD, 10, 10, dolfinx.mesh.CellType.triangle, dtype=np.float64)
el = basix.ufl.quadrature_element(
scheme="default", degree=3, cell=mesh.basix_cell(), value_shape=()
)
V = dolfinx.fem.functionspace(mesh, el)
u = dolfinx.fem.Function(V)
u.interpolate(lambda x: np.sin(np.pi * x[0]) * np.sin(np.pi * x[1]))
One option that already exists with the current DOLFINx/Pyvista API is to plot them as points
import pyvista
plotter = pyvista.Plotter()
plotter.add_points(
V.tabulate_dof_coordinates(),
scalars=u.x.array,
render_points_as_spheres=True,
point_size=20,
show_scalar_bar=False,
)
if not pyvista.OFF_SCREEN:
plotter.show()
2026-08-05 06:37:42.901 ( 0.565s) [ 7FBAF9CDA140]vtkXOpenGLRenderWindow.:1460 WARN| bad X server connection. DISPLAY=:99.0
INFO:trame_server.utils.namespace:Translator(prefix=None)
---------------------------------------------------------------------------
FileNotFoundError Traceback (most recent call last)
File /dolfinx-env/lib/python3.12/site-packages/aiohttp/web_urldispatcher.py:546, in StaticResource.__init__(self, prefix, directory, name, expect_handler, chunk_size, show_index, follow_symlinks, append_version)
545 try:
--> 546 directory = Path(directory).expanduser().resolve(strict=True)
547 except FileNotFoundError as error:
File /usr/lib/python3.12/pathlib.py:1242, in Path.resolve(self, strict)
1241 try:
-> 1242 s = self._flavour.realpath(self, strict=strict)
1243 except OSError as e:
File <frozen posixpath>:432, in realpath(filename, strict)
File <frozen posixpath>:477, in _joinrealpath(path, rest, strict, seen)
FileNotFoundError: [Errno 2] No such file or directory: '/dolfinx-env/lib/python3.12/site-packages/trame_client/module/vue3-www'
The above exception was the direct cause of the following exception:
ValueError Traceback (most recent call last)
Cell In[5], line 10
6 point_size=20,
7 show_scalar_bar=False,
8 )
9 if not pyvista.OFF_SCREEN:
---> 10 plotter.show()
File /dolfinx-env/lib/python3.12/site-packages/pyvista/_deprecate_positional_args.py:243, in _deprecate_positional_args.<locals>._inner_deprecate_positional_args.<locals>.inner_f(*args, **kwargs)
239 warn_external(msg, PyVistaDeprecationWarning)
241 warn_positional_args()
--> 243 return f(*args, **kwargs)
File /dolfinx-env/lib/python3.12/site-packages/pyvista/plotting/plotter.py:8231, in Plotter.show(self, title, window_size, interactive, auto_close, interactive_update, full_screen, screenshot, return_img, cpos, jupyter_backend, return_viewer, return_cpos, before_close_callback, store_image_depth, **kwargs)
8228 jupyter_backend = self._theme.jupyter_backend
8230 if jupyter_backend is None or jupyter_backend.lower() != 'none':
-> 8231 jupyter_disp = handle_plotter(self, backend=jupyter_backend, **jupyter_kwargs)
8233 self.render()
8235 # initial double render needed for certain passes when offscreen
File /dolfinx-env/lib/python3.12/site-packages/pyvista/jupyter/notebook.py:74, in handle_plotter(plotter, backend, screenshot, **kwargs)
71 if backend in ['server', 'client', 'trame', 'html']:
72 from pyvista.trame.jupyter import show_trame # noqa: PLC0415
---> 74 return show_trame(plotter, mode=backend, **kwargs)
76 except ImportError as e:
77 # Trame was explicitly requested but not available
78 _ensure_entry_points()
File /dolfinx-env/lib/python3.12/site-packages/pyvista/trame/jupyter.py:394, in show_trame(plotter, mode, name, server_proxy_enabled, server_proxy_prefix, jupyter_extension_enabled, collapse_menu, add_menu, add_menu_items, default_server_rendering, handler, **kwargs)
391 kwargs.setdefault('height', dh)
393 if mode == 'html':
--> 394 return EmbeddableWidget(plotter, **kwargs)
396 if jupyter_extension_enabled is None:
397 jupyter_extension_enabled = pv.global_theme.trame.jupyter_extension_enabled
File /dolfinx-env/lib/python3.12/site-packages/pyvista/trame/jupyter.py:137, in EmbeddableWidget.__init__(self, plotter, width, height, **kwargs)
135 msg = 'Please install `ipywidgets`.'
136 raise ImportError(msg)
--> 137 scene = plotter.export_html(filename=None)
138 src = scene.getvalue().replace('"', '"')
139 # eventually we could maybe expose this, but for now make sure we're at least
140 # consistent with matplotlib's color (light gray)
File /dolfinx-env/lib/python3.12/site-packages/pyvista/plotting/plotter.py:784, in BasePlotter.export_html(self, filename)
781 msg = 'Please install trame dependencies: pip install "pyvista[jupyter]"'
782 raise ImportError(msg)
--> 784 data = self.export_vtksz(filename=None)
785 buffer = io.StringIO()
786 write_html(data, buffer)
File /dolfinx-env/lib/python3.12/site-packages/pyvista/plotting/plotter.py:837, in BasePlotter.export_vtksz(self, filename, format)
835 server = get_server(pv.global_theme.trame.jupyter_server_name)
836 if not server.running:
--> 837 elegantly_launch(pv.global_theme.trame.jupyter_server_name)
839 view = PyVistaLocalView(self, trame_server=server)
841 content = view.export(format=format)
File /dolfinx-env/lib/python3.12/site-packages/pyvista/trame/jupyter.py:487, in elegantly_launch(*args, **kwargs)
484 # Basically monkey patches asyncio to support this
485 nest_asyncio2.apply()
--> 487 return asyncio.run(launch_it())
File /dolfinx-env/lib/python3.12/site-packages/nest_asyncio2.py:115, in run(main, debug, loop_factory)
113 task = asyncio.ensure_future(main, loop=loop)
114 try:
--> 115 return loop.run_until_complete(task)
116 finally:
117 if not task.done():
File /dolfinx-env/lib/python3.12/site-packages/nest_asyncio2.py:230, in _patch_loop.<locals>.run_until_complete(self, future)
227 if self._ready:
228 self._run_once()
--> 230 return f.result()
File /usr/lib/python3.12/asyncio/futures.py:203, in Future.result(self)
201 self.__log_traceback = False
202 if self._exception is not None:
--> 203 raise self._exception.with_traceback(self._exception_tb)
204 return self._result
File /usr/lib/python3.12/asyncio/tasks.py:314, in Task.__step_run_and_handle_result(***failed resolving arguments***)
310 try:
311 if exc is None:
312 # We use the `send` method directly, because coroutines
313 # don't have `__iter__` and `__next__` methods.
--> 314 result = coro.send(None)
315 else:
316 result = coro.throw(exc)
File /dolfinx-env/lib/python3.12/site-packages/pyvista/trame/jupyter.py:482, in elegantly_launch.<locals>.launch_it()
481 async def launch_it():
--> 482 await launch_server(*args, **kwargs).ready
File /dolfinx-env/lib/python3.12/site-packages/pyvista/trame/jupyter.py:215, in launch_server(server, port, host, wslink_backend, **kwargs)
213 if server._running_stage == 0:
214 server.controller.on_server_ready.add(on_ready)
--> 215 server.start(
216 exec_mode='task',
217 host=host,
218 port=port,
219 open_browser=False,
220 show_connection_info=False,
221 disable_logging=True,
222 timeout=0,
223 backend=wslink_backend,
224 )
225 # else, server is already running or launching
226 return server
File /dolfinx-env/lib/python3.12/site-packages/trame_server/core.py:725, in Server.start(self, port, thread, open_browser, show_connection_info, disable_logging, backend, follow_symlinks, exec_mode, timeout, host, **kwargs)
722 CoreServer.configure(options)
724 self._running_stage = 1
--> 725 task = CoreServer.server_start(
726 options,
727 **{ # Do a proper merging/override
728 **kwargs,
729 "disableLogging": disable_logging,
730 "backend": backend,
731 "exec_mode": exec_mode,
732 },
733 )
735 # Manage exit life cycle unless coroutine
736 if exec_mode == "main":
File /dolfinx-env/lib/python3.12/site-packages/trame_server/protocol.py:52, in CoreServer.server_start(options, disableLogging, backend, exec_mode, **kwargs)
43 @staticmethod
44 def server_start(
45 options,
(...) 50 ):
51 # NOTE: **kwargs to wslink's start_webserver are currently unused
---> 52 return server.start_webserver(
53 options=options,
54 protocol=CoreServer,
55 disableLogging=disableLogging,
56 backend=backend,
57 exec_mode=exec_mode,
58 **kwargs,
59 )
File /dolfinx-env/lib/python3.12/site-packages/wslink/server.py:257, in start_webserver(options, protocol, disableLogging, backend, exec_mode, **_)
254 server_config["handle_signals"] = not options.nosignalhandlers
256 # Create the webserver and start it
--> 257 ws_server = create_webserver(server_config, backend=backend)
259 # Once we have python 3.7 minimum, we can start the server with asyncio.run()
260 # asyncio.run(ws_server.start())
261
262 # Until then, we can start the server this way
263 try:
File /dolfinx-env/lib/python3.12/site-packages/wslink/server.py:168, in create_webserver(server_config, backend)
167 def create_webserver(server_config, backend="aiohttp"):
--> 168 return backends.create_webserver(server_config, backend=backend)
File /dolfinx-env/lib/python3.12/site-packages/wslink/backends/__init__.py:5, in create_webserver(server_config, backend)
2 if backend == "aiohttp":
3 from .aiohttp import create_webserver # noqa: PLC0415
----> 5 return create_webserver(server_config)
7 if backend == "generic":
8 from .generic import create_webserver # noqa: PLC0415
File /dolfinx-env/lib/python3.12/site-packages/wslink/backends/aiohttp/__init__.py:218, in create_webserver(server_config)
215 return ReverseWebAppServer(server_config)
217 # Normal web server
--> 218 return WebAppServer(server_config)
File /dolfinx-env/lib/python3.12/site-packages/wslink/backends/aiohttp/__init__.py:118, in WebAppServer.__init__(self, server_config)
116 # Resolve / => index.html
117 self.app.router.add_route("GET", "/", _root_handler)
--> 118 self.app.add_routes(routes)
120 self.app[STATE_KEY] = {}
File /dolfinx-env/lib/python3.12/site-packages/aiohttp/web_app.py:379, in Application.add_routes(self, routes)
378 def add_routes(self, routes: Iterable[AbstractRouteDef]) -> list[AbstractRoute]:
--> 379 return self.router.add_routes(routes)
File /dolfinx-env/lib/python3.12/site-packages/aiohttp/web_urldispatcher.py:1259, in UrlDispatcher.add_routes(self, routes)
1257 registered_routes = []
1258 for route_def in routes:
-> 1259 registered_routes.extend(route_def.register(self))
1260 return registered_routes
File /dolfinx-env/lib/python3.12/site-packages/aiohttp/web_routedef.py:87, in StaticDef.register(self, router)
86 def register(self, router: UrlDispatcher) -> list[AbstractRoute]:
---> 87 resource = router.add_static(self.prefix, self.path, **self.kwargs)
88 routes = resource.get_info().get("routes", {})
89 return list(routes.values())
File /dolfinx-env/lib/python3.12/site-packages/aiohttp/web_urldispatcher.py:1183, in UrlDispatcher.add_static(self, prefix, path, name, expect_handler, chunk_size, show_index, follow_symlinks, append_version)
1181 if prefix.endswith("/"):
1182 prefix = prefix[:-1]
-> 1183 resource = StaticResource(
1184 prefix,
1185 path,
1186 name=name,
1187 expect_handler=expect_handler,
1188 chunk_size=chunk_size,
1189 show_index=show_index,
1190 follow_symlinks=follow_symlinks,
1191 append_version=append_version,
1192 )
1193 self.register_resource(resource)
1194 return resource
File /dolfinx-env/lib/python3.12/site-packages/aiohttp/web_urldispatcher.py:548, in StaticResource.__init__(self, prefix, directory, name, expect_handler, chunk_size, show_index, follow_symlinks, append_version)
546 directory = Path(directory).expanduser().resolve(strict=True)
547 except FileNotFoundError as error:
--> 548 raise ValueError(f"'{directory}' does not exist") from error
549 if not directory.is_dir():
550 raise ValueError(f"'{directory}' is not a directory")
ValueError: '/dolfinx-env/lib/python3.12/site-packages/trame_client/module/vue3-www' does not exist
Using scifem, we can write the point cloud data to an XDMFFile that can be opened with Paraview.
with scifem.xdmf.XDMFFile("point_cloud.xdmf", [u]) as xdmf:
xdmf.write(0.0)
The point cloud can now be loaded into ParaView for visualization, by selecting “Point Gaussian” as the representation.

We can write any dolfinx.fem.FunctionSpace that has support for tabulate_dof_coordinates to a point cloud.
For example, we can create a higher order Lagrange space, and write two functions to file.
Q = dolfinx.fem.functionspace(mesh, ("Lagrange", 3, (2, )))
q_1 = dolfinx.fem.Function(Q, name="q_sin")
q_1.interpolate(lambda x: (np.sin(np.pi * x[0]), np.sin(np.pi * x[1])))
q_2 = dolfinx.fem.Function(Q, name="q_cos")
q_2.interpolate(lambda x: (np.cos(np.pi * x[0]), np.cos(np.pi * x[1])))
We write these two functions to file as illustrated above
yielding the following point clouds in ParaView after applying glyphs.

If you have time dependent data, you can write multiple time steps to the same file using e.g
If you need to keep the file open for longer, you can use the following syntax