Repository: astropy/astropy

Issue:
# Control display of vector columns in TimeSeries

### What is the problem this feature will solve?

At present, vector columns show only the first and last element; the rest are elided with "..". This behavior is [hardwired in the code](https://github.com/astropy/astropy/blob/main/astropy/table/pprint.py#L533).

### Describe the desired outcome

Set an option, something like `np.set_printoptions(threshhold=np.inf)` or similar, to change the number of elements shown, or to specify a linewidth, etc.

### Additional context

An example of need is position or velocity three-vectors. For most screens, there is enough width to display the middle element, particularly if formatting is set to show a modest number of digits, and this is more informative.

Implementation patch:
diff --git a/astropy/table/__init__.py b/astropy/table/__init__.py
index 45dda58923b4..e02cda7c15a2 100644
--- a/astropy/table/__init__.py
+++ b/astropy/table/__init__.py
@@ -68,6 +68,7 @@ class Conf(_config.ConfigNamespace):
         "'always', 'slice', 'refcount', 'attributes'.",
         "string_list",
     )
+
     replace_inplace = _config.ConfigItem(
         False,
         "Always use in-place update of a table column when using setitem, "
@@ -77,6 +78,15 @@ class Conf(_config.ConfigNamespace):
         "subsequent major releases.",
     )
 
+    format_size_threshold = _config.ConfigItem(
+        1,
+        "Maximum total size (product of all dimensions except the first) for displaying full multidimensional column elements. "
+        "If the size exceeds this threshold, only the first and last elements are shown with '..'. "
+        "Otherwise, the full element is displayed. Default is 1 which shows only first and last (e.g., '1 .. 5'). "
+        "Set to a large value like sys.maxsize for no limit.",
+        cfgtype="integer",
+    )
+
 
 conf = Conf()
 
diff --git a/astropy/table/pprint.py b/astropy/table/pprint.py
index 92946c097f8f..a059ff21b14a 100644
--- a/astropy/table/pprint.py
+++ b/astropy/table/pprint.py
@@ -8,7 +8,7 @@
 
 import numpy as np
 
-from astropy import log
+from astropy import log, table
 from astropy.utils.console import Getch, color_print, conf
 from astropy.utils.data_info import dtype_info_name
 
@@ -176,12 +176,12 @@ def _get_pprint_size(max_lines=None, max_width=None):
         If no value of ``max_lines`` is supplied then the height of the
         screen terminal is used to set ``max_lines``.  If the terminal
         height cannot be determined then the default will be determined
-        using the ``astropy.table.conf.max_lines`` configuration item. If a
+        using the ``astropy.console.max_lines`` console configuration item. If a
         negative value of ``max_lines`` is supplied then there is no line
         limit applied.
 
         The same applies for max_width except the configuration item is
-        ``astropy.table.conf.max_width``.
+        ``astropy.console.conf.max_width``.
 
         Parameters
         ----------
@@ -518,23 +518,40 @@ def _pformat_col_iter(
             indices = np.arange(n_rows)
 
         def format_col_str(idx):
-            if multidims:
-                # Prevents columns like Column(data=[[(1,)],[(2,)]], name='a')
-                # with shape (n,1,...,1) from being printed as if there was
-                # more than one element in a row
-                if multidims_all_ones:
-                    return format_func(col_format, col[(idx,) + multidim0])
-                elif multidims_has_zero:
-                    # Any zero dimension means there is no data to print
-                    return ""
-                else:
-                    left = format_func(col_format, col[(idx,) + multidim0])
-                    right = format_func(col_format, col[(idx,) + multidim1])
-                    return f"{left} .. {right}"
-            elif is_scalar:
-                return format_func(col_format, col)
+            if not multidims:
+                return format_func(col_format, col if is_scalar else col[idx])
+
+            # Prevents columns like Column(data=[[(1,)],[(2,)]], name='a')
+            # with shape (n,1,...,1) from being printed as if there was
+            # more than one element in a row
+            if multidims_all_ones:
+                return format_func(col_format, col[(idx,) + multidim0])
+
+            if multidims_has_zero:
+                # Any zero dimension means there is no data to print
+                return ""
+
+            size = np.prod(multidims)
+            # Uses astropy.table.conf.format_size_threshold for multidimensional column formatting
+            threshold = table.conf.format_size_threshold
+
+            # If size > threshold, revert to legacy "first .. last" format
+            if size > threshold:
+                left = format_func(col_format, col[(idx,) + multidim0])
+                right = format_func(col_format, col[(idx,) + multidim1])
+                result = f"{left} .. {right}"
             else:
-                return format_func(col_format, col[idx])
+
+                def format_multidim(arr):
+                    """Recursively format multidimensional arrays."""
+                    if arr.ndim == 0:
+                        return format_func(col_format, arr)
+                    else:
+                        return "[" + " ".join(format_multidim(val) for val in arr) + "]"
+
+                result = format_multidim(col[idx])
+
+            return result
 
         # Add formatted values if within bounds allowed by max_lines
         for idx in indices:


Test patch:
diff --git a/astropy/table/tests/test_pprint.py b/astropy/table/tests/test_pprint.py
index 9f9819345c1b..0df67673288b 100644
--- a/astropy/table/tests/test_pprint.py
+++ b/astropy/table/tests/test_pprint.py
@@ -1,17 +1,19 @@
 # Licensed under a 3-clause BSD style license - see LICENSE.rst
 
 
+import sys
 from io import StringIO
 from shutil import get_terminal_size
 
 import numpy as np
 import pytest
 
-from astropy import conf, table
+from astropy import table
 from astropy import units as u
 from astropy.io import ascii
 from astropy.table import Column, QTable, Table
 from astropy.table.table_helpers import simple_table
+from astropy.utils.console import conf as console_conf
 from astropy.utils.exceptions import AstropyDeprecationWarning
 
 BIG_WIDE_ARR = np.arange(2000, dtype=np.float64).reshape(100, 20)
@@ -162,7 +164,10 @@ def test_format0(self, table_type):
         """
         self._setup(table_type)
         arr = np.arange(4000, dtype=np.float64).reshape(100, 40)
-        with conf.set_temp("max_width", None), conf.set_temp("max_lines", None):
+        with (
+            console_conf.set_temp("max_width", None),
+            console_conf.set_temp("max_lines", None),
+        ):
             lines = table_type(arr).pformat(max_lines=None, max_width=None)
         width, nlines = get_terminal_size()
         assert len(lines) == nlines
@@ -391,9 +396,7 @@ def test_column_format(self, table_type):
         assert t["a"].format == "%4.2f {0:}"  # format did not change
 
     def test_column_format_with_threshold(self, table_type):
-        from astropy import conf
-
-        with conf.set_temp("max_lines", 8):
+        with console_conf.set_temp("max_lines", 8):
             t = table_type([np.arange(20)], names=["a"])
             t["a"].format = "%{0:}"
             assert str(t["a"]).splitlines() == [
@@ -520,9 +523,7 @@ def test_column_format(self):
         assert str(t["a"]) == "   a   \n-------\n     --\n%4.2f 2\n     --"
 
     def test_column_format_with_threshold_masked_table(self):
-        from astropy import conf
-
-        with conf.set_temp("max_lines", 8):
+        with console_conf.set_temp("max_lines", 8):
             t = Table([np.arange(20)], names=["a"], masked=True)
             t["a"].format = "%{0:}"
             t["a"].mask[0] = True
@@ -1141,3 +1142,124 @@ def test_zero_length_string():
         "          12",
     ]
     assert t.pformat(show_dtype=True) == exp
+
+
+def test_multidim_threshold_default():
+    """Test default behavior (threshold=1) shows only first and last elements"""
+    # Default threshold is 1, so size > 1 will show "first .. last"
+    # Uses astropy.table.conf.format_size_threshold
+    with table.conf.set_temp("format_size_threshold", 1):
+        data = np.array([[1, 2, 3, 4, 5], [6, 7, 8, 9, 10]])
+        col = Column(data, name="test")
+        lines = col.pformat(show_name=False, show_unit=False)
+        assert [line.strip() for line in lines] == ["1 .. 5", "6 .. 10"]
+
+
+def test_multidim_threshold_show_all():
+    """Test large threshold shows all elements"""
+    # Use sys.maxsize or a large value to show all elements
+    # Uses astropy.table.conf.format_size_threshold
+    with table.conf.set_temp("format_size_threshold", sys.maxsize):
+        data = np.array([[1, 2, 3, 4, 5], [6, 7, 8, 9, 10]])
+        col = Column(data, name="test")
+        lines = col.pformat(show_name=False, show_unit=False)
+        assert [line.strip() for line in lines] == ["[1 2 3 4 5]", "[6 7 8 9 10]"]
+
+
+def test_multidim_threshold_partial():
+    """Test threshold shows elements with ellipsis when size > threshold"""
+    # Size is 10 (10 elements in the array), threshold is 4
+    # Since 10 > 4, should show "first .. last"
+    # Uses astropy.table.conf.format_size_threshold
+    with table.conf.set_temp("format_size_threshold", 4):
+        data = np.array([[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]])
+        col = Column(data, name="test")
+        lines = col.pformat(show_name=False, show_unit=False)
+        assert [line.strip() for line in lines] == ["0 .. 9"]
+
+
+def test_multidim_threshold_three_vectors():
+    """Test threshold=3 for 3-element vectors (common use case)"""
+    # Uses astropy.table.conf.format_size_threshold
+    with table.conf.set_temp("format_size_threshold", 3):
+        data = np.array([[1, 2, 3], [4, 5, 6]])
+        col = Column(data, name="position")
+        lines = col.pformat(show_name=False, show_unit=False)
+        assert lines == ["[1 2 3]", "[4 5 6]"]
+
+
+def test_multidim_threshold_with_formatting():
+    """Test threshold works with column formatting"""
+    # Uses astropy.table.conf.format_size_threshold
+    with table.conf.set_temp("format_size_threshold", sys.maxsize):
+        data = np.array([[1.23456, 2.34567, 3.45678]], dtype=float)
+        col = Column(data, name="float", format="%.2f")
+        lines = col.pformat(show_name=False, show_unit=False)
+        assert lines == ["[1.23 2.35 3.46]"]
+
+
+def test_multidim_threshold_2x3_array():
+    """Test 2x3 array display with different thresholds"""
+    # 2x3 array has size = 2*3 = 6
+    data = np.array([[[1, 2, 3], [4, 5, 6]], [[7, 8, 9], [10, 11, 12]]])
+    col = Column(data, name="test")
+
+    # With threshold=1, size=6 > 1, so show "first .. last"
+    # Uses astropy.table.conf.format_size_threshold
+    with table.conf.set_temp("format_size_threshold", 1):
+        lines = col.pformat(show_name=False, show_unit=False)
+        assert [line.strip() for line in lines] == ["1 .. 6", "7 .. 12"]
+
+    # With threshold=6, size=6 <= 6, so show full element
+    # Uses astropy.table.conf.format_size_threshold
+    with table.conf.set_temp("format_size_threshold", 6):
+        lines = col.pformat(show_name=False, show_unit=False)
+        assert [line.strip() for line in lines] == [
+            "[[1 2 3] [4 5 6]]",
+            "[[7 8 9] [10 11 12]]",
+        ]
+
+    # With threshold=5, size=6 > 5, so show "first .. last"
+    # Uses astropy.table.conf.format_size_threshold
+    with table.conf.set_temp("format_size_threshold", 5):
+        lines = col.pformat(show_name=False, show_unit=False)
+        assert [line.strip() for line in lines] == ["1 .. 6", "7 .. 12"]
+
+
+def test_multidim_threshold_3d_array():
+    """Test 3D array display.
+
+    Each table element has shape (2, 3, 2); the full column shape is (2, 2, 3, 2)
+    where the first dimension (2) is the number of rows, and the remaining dimensions
+    (2, 3, 2) define the shape of each element. Size = 2*3*2 = 12.
+    """
+    data = np.arange(2 * 2 * 3 * 2).reshape(2, 2, 3, 2)
+    col = Column(data, name="test")
+
+    # With threshold=1, size=12 > 1, so show "first .. last"
+    # Uses astropy.table.conf.format_size_threshold
+    with table.conf.set_temp("format_size_threshold", 1):
+        lines = col.pformat(show_name=False, show_unit=False)
+        assert [line.strip() for line in lines] == ["0 .. 11", "12 .. 23"]
+
+    # With threshold=12, size=12 <= 12, so show full element with brackets
+    # Uses astropy.table.conf.format_size_threshold
+    with table.conf.set_temp("format_size_threshold", 12):
+        lines = col.pformat(show_name=False, show_unit=False)
+        # Each row has shape (2, 3, 2) = [[[0,1] [2,3] [4,5]] [[6,7] [8,9] [10,11]]]
+        assert [line.strip() for line in lines] == [
+            "[[[0 1] [2 3] [4 5]] [[6 7] [8 9] [10 11]]]",
+            "[[[12 13] [14 15] [16 17]] [[18 19] [20 21] [22 23]]]",
+        ]
+
+
+def test_multidim_threshold_2x2_array_with_threshold_4():
+    """Test that 2x2 array (size=4) is fully shown when threshold=4"""
+    data = np.array([[[1, 2], [3, 4]], [[5, 6], [7, 8]]])
+    col = Column(data, name="test")
+
+    # With threshold=4, size=4 <= 4, so show full element
+    # Uses astropy.table.conf.format_size_threshold
+    with table.conf.set_temp("format_size_threshold", 4):
+        lines = col.pformat(show_name=False, show_unit=False)
+        assert lines == ["[[1 2] [3 4]]", "[[5 6] [7 8]]"]