Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix LaTeX pane MathJax rendering #7188

Merged
merged 2 commits into from
Aug 27, 2024
Merged
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
17 changes: 2 additions & 15 deletions panel/models/mathjax.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,10 @@
"""
Defines a custom MathJax bokeh model to render text using MathJax.
"""
from bokeh.models import Markup
from bokeh.models import Div


class MathJax(Markup):
class MathJax(Div):
"""
A bokeh model that renders text using MathJax.
"""

__javascript__ = [
"https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/MathJax.js?config=TeX-MML-AM_CHTML"
]

__js_skip__ = {'MathJax': __javascript__}

__js_require__ = {
'paths': {
'mathjax': "//cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/MathJax.js?config=TeX-AMS_HTML"
},
'shim': {'mathjax': {'exports': "MathJax"}}
}
16 changes: 15 additions & 1 deletion panel/models/mathjax.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,21 @@ export class MathJaxView extends PanelMarkupView {

override render(): void {
super.render()
this.container.innerHTML = this.has_math_disabled() ? this.model.text : this.process_tex(this.model.text)
const text = this.model.text
const tex_parts = this.provider.MathJax.find_tex(text)
const processed_text: string[] = []

let last_index: number | undefined = 0
for (const part of tex_parts) {
processed_text.push(text.slice(last_index, part.start.n))
processed_text.push(this.provider.MathJax.tex2svg(part.math, {display: part.display}).outerHTML)
last_index = part.end.n
}
if (last_index! < text.length) {
processed_text.push(text.slice(last_index))
}

this.container.innerHTML = processed_text.join("")
}
}

Expand Down
25 changes: 4 additions & 21 deletions panel/pane/equation.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
"""
from __future__ import annotations

import re
import sys

from typing import (
Expand All @@ -24,15 +23,6 @@
from bokeh.model import Model


def is_sympy_expr(obj: Any) -> bool:
"""Test for sympy.Expr types without usually needing to import sympy"""
if 'sympy' in sys.modules and 'sympy' in str(type(obj).__class__):
import sympy # type: ignore
if isinstance(obj, sympy.Expr):
return True
return False


class LaTeX(ModelPane):
r"""
The `LaTeX` pane allows rendering LaTeX equations. It uses either
Expand Down Expand Up @@ -71,27 +61,21 @@ class LaTeX(ModelPane):

@classmethod
def applies(cls, obj: Any) -> float | bool | None:
if is_sympy_expr(obj) or hasattr(obj, '_repr_latex_'):
if hasattr(obj, '_repr_latex_'):
return 0.05
elif isinstance(obj, str):
return None
else:
return False

def _process_param_change(self, params) -> dict[str, Any]:
if self.renderer == "mathjax":
# Replace $$math$$ with \[math\] and $math$ with \(math\)
msg = re.sub(r"(\$\$)(.*?)(\$\$)", r"\[\2\]", params["object"])
params["object"] = re.sub(r"(\$)(.*?)(\$)", r"\(\2\)", msg)
return super()._process_param_change(params)

def _get_model_type(self, root: Model, comm: Comm | None) -> type[Model]:
module = self.renderer
if module is None:
if 'panel.models.mathjax' in sys.modules and 'panel.models.katex' not in sys.modules:
module = 'mathjax'
else:
module = 'katex'
self.renderer = module
model = 'KaTeX' if module == 'katex' else 'MathJax'
return lazy_load(f'panel.models.{module}', model, isinstance(comm, JupyterComm), root)

Expand All @@ -110,7 +94,6 @@ def _transform_object(self, obj: Any) -> dict[str, Any]:
obj = ''
elif hasattr(obj, '_repr_latex_'):
obj = obj._repr_latex_()
elif is_sympy_expr(obj):
import sympy
obj = r'$'+sympy.latex(obj)+'$'
if self.renderer == 'mathjax' and obj.startswith('$') and not obj.startswith('$$'):
obj = f'${obj}$'
return dict(object=obj)
2 changes: 1 addition & 1 deletion panel/tests/pane/test_equation.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ def test_latex_mathjax_pane(document, comm):
assert pane._models[model.ref['id']][0] is model
assert type(model).__name__ == 'MathJax'
# assert model.text == r"$\frac{p^3}{q}$"
assert model.text == r"\(\frac{p^3}{q}\)"
assert model.text == r"$$\frac{p^3}{q}$$"

# Cleanup
pane._cleanup(model)
Expand Down
24 changes: 24 additions & 0 deletions panel/tests/ui/pane/test_equation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import pytest

pytest.importorskip("playwright")

from playwright.sync_api import expect

from panel.pane import LaTeX
from panel.tests.util import serve_component

pytestmark = pytest.mark.ui

def test_latex_mathjax_renderer(page):
ltx = LaTeX('$1+1$', renderer='mathjax')

serve_component(page, ltx)

expect(page.locator('mjx-container')).to_have_count(1)

def test_latex_katex_renderer(page):
ltx = LaTeX('$1+1$', renderer='katex')

serve_component(page, ltx)

expect(page.locator('.katex-html')).to_have_count(1)
Loading