Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions docs/options/render_math.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
---
jupytext:
formats: docs///md:myst,docs/py///py:percent
notebook_metadata_filter: -jupytext.text_representation.jupytext_version
text_representation:
extension: .md
format_name: myst
format_version: 0.13
kernelspec:
display_name: itables
language: python
name: itables
---

# Rendering Mathematical Formulae

To render mathematical contents like equations in your DataFrame (rows or header), use the `render_math` option:

```{code-cell}
import pandas as pd

import itables

itables.init_notebook_mode()

itables.show(
pd.DataFrame(
{
"$N_{\\text{event}}$": ["$\\alpha$", "$\\beta$", "$\\gamma$"] * 10,
"Value": [
"$0.8_{-0.1}^{+0.3}$",
"$3.2_{-0.4}^{+0.2}$",
"$-0.1_{-0.5}^{+0.8}$",
]
* 10,
}
),
render_math=True,
)
```
41 changes: 41 additions & 0 deletions docs/py/options/render_math.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# ---
# jupyter:
# jupytext:
# formats: docs///md:myst,docs/py///py:percent
# notebook_metadata_filter: -jupytext.text_representation.jupytext_version
# text_representation:
# extension: .py
# format_name: percent
# format_version: '1.3'
# kernelspec:
# display_name: itables
# language: python
# name: itables
# ---

# %% [markdown]
# # Rendering Mathematical Formulae
#
# To render mathematical contents like equations in your DataFrame (rows or header), use the `render_math` option:

# %%
import pandas as pd

import itables

itables.init_notebook_mode()

itables.show(
pd.DataFrame(
{
"$N_{\\text{event}}$": ["$\\alpha$", "$\\beta$", "$\\gamma$"] * 10,
"Value": [
"$0.8_{-0.1}^{+0.3}$",
"$3.2_{-0.4}^{+0.2}$",
"$-0.1_{-0.5}^{+0.8}$",
]
* 10,
}
),
render_math=True,
)
74 changes: 73 additions & 1 deletion packages/dt_for_itables/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion packages/dt_for_itables/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@
"datatables.net-searchpanes-dt": "^2.3.3",
"datatables.net-select-dt": "^3.0.0",
"jquery": "^3.7.1",
"jszip": "^3.10.1"
"jszip": "^3.10.1",
"mathjax-full": "^3.2.2"
},
"devDependencies": {
"esbuild": "^0.25.3"
Expand Down
82 changes: 80 additions & 2 deletions packages/dt_for_itables/src/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
import JSZip from 'jszip';
import jQuery from 'jquery';

// Add MathJax dependency
import { mathjax } from 'mathjax-full/js/mathjax.js';
import { TeX } from 'mathjax-full/js/input/tex.js';
import { SVG } from 'mathjax-full/js/output/svg.js';
import { liteAdaptor } from 'mathjax-full/js/adaptors/liteAdaptor.js';
import { RegisterHTMLHandler } from 'mathjax-full/js/handlers/html.js';
import { AllPackages } from 'mathjax-full/js/input/tex/AllPackages.js';

import DataTable from 'datatables.net-dt';
import 'datatables.net-dt/css/dataTables.dataTables.min.css';

Expand Down Expand Up @@ -80,9 +88,48 @@ function evalNestedKeys(obj, keys, context) {
}
}

class MathJaxProcessor {
constructor() {
this.adaptor = liteAdaptor();
RegisterHTMLHandler(this.adaptor);

this.tex = new TeX({
packages: AllPackages,
inlineMath: [['$', '$'], ['\\(', '\\)']],
displayMath: [['$$', '$$'], ['\\[', '\\]']],
processEscapes: true
});

this.svg = new SVG({ fontCache: 'none' });
this.html = mathjax.document('', { InputJax: this.tex, OutputJax: this.svg });
}

convertLateXtoHTML(cellContent) {
if (!cellContent || typeof cellContent !== 'string') {
return cellContent;
}

// Process inline math: $...$
cellContent = cellContent.replace(/\$([^\$]+)\$/g, (match, latex) => {
const node = this.html.convert(latex, { display: false });
return this.adaptor.outerHTML(node);
});

// Process display math: $$...$$
cellContent = cellContent.replace(/\$\$([\s\S]+?)\$\$/g, (match, latex) => {
const node = this.html.convert(latex, { display: true });
return this.adaptor.outerHTML(node);
});

return cellContent;
}
}

const mathProcessor = new MathJaxProcessor();

class ITable {
constructor(table, itable_args) {
const { data, caption, classes, style, data_json, table_html, table_style, selected_rows, filtered_row_count, keys_to_be_evaluated, column_filters, text_in_header_can_be_selected, initComplete, downsampling_warning, ...dt_args } = itable_args;
const { data, caption, classes, style, data_json, table_html, table_style, selected_rows, filtered_row_count, keys_to_be_evaluated, column_filters, text_in_header_can_be_selected, initComplete, downsampling_warning, render_math = false, ...dt_args } = itable_args;
if (data !== undefined) {
throw new Error("The 'data' property is not allowed in dt_args.");
}
Expand All @@ -95,6 +142,37 @@ class ITable {

this.filtered_row_count = filtered_row_count || 0;

// Store render_math flag for later use in drawCallback
this.render_math = render_math;

// Add drawCallback for math rendering
const originalDrawCallback = dt_args.drawCallback;
dt_args.drawCallback = (settings) => {
// Call original drawCallback if it exists
if (originalDrawCallback) {
originalDrawCallback.call(this.dt, settings);
}

// Process math in visible cells
if (this.render_math) {
jQuery(settings.nTable).find('tbody td').each((i, cell) => {
const $cell = jQuery(cell);
const cellText = $cell.text();

// Only process cells that appear to contain LaTeX
if (cellText.includes('$')) {
// Store original HTML to avoid reprocessing
if (!$cell.data('original-content')) {
$cell.data('original-content', $cell.html());
}

// Replace with rendered math
$cell.html(mathProcessor.convertLateXtoHTML($cell.data('original-content')));
}
});
}
};

if (text_in_header_can_be_selected || column_filters) {
dt_args.initComplete = function (settings, json) {
if (column_filters == 'header') {
Expand Down Expand Up @@ -161,7 +239,7 @@ class ITable {

this.table = jQuery(table);
if (table_html) {
this.table.html(table_html);
this.table.html(render_math ? mathProcessor.convertLateXtoHTML(table_html) : table_html);
}
if (classes) {
classes.forEach(element => {
Expand Down
3 changes: 2 additions & 1 deletion packages/itables_anywidget/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion packages/itables_for_dash/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion packages/itables_for_streamlit/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions src/itables/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@
"""
allow_html: bool = False

"""Render LaTeX expressions using MathJax."""
render_math: bool = False

"""Optional table footer"""
footer: bool = False

Expand Down
2 changes: 2 additions & 0 deletions src/itables/typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ class ITableOptions(DataTableOptions):
maxColumns: NotRequired[int]

allow_html: NotRequired[bool]
render_math: NotRequired[bool]

table_id: NotRequired[str]
dt_url: NotRequired[str]
Expand Down Expand Up @@ -136,6 +137,7 @@ class DTForITablesOptions(DataTableOptions):

column_filters: NotRequired[Literal[False, "header", "footer"]]
keys_to_be_evaluated: NotRequired[Sequence[Sequence[Union[int, str]]]]
render_math: NotRequired[bool]

# These options are used in the HTML template
# and don't reach the ITable JavaScript class
Expand Down
Loading