.. currentmodule:: altair

.. _user-guide-text-marks:

Text
~~~~~~
``text`` mark represents each data point with a text instead of a point.

Text Mark Properties
--------------------
.. altair-plot::
    :hide-code:
    :div_class: properties-example

    import altair as alt
    import pandas as pd

    angle_slider = alt.binding_range(min=-180, max=180, step=1)
    angle_var = alt.param(bind=angle_slider, value=0, name="angle")

    dx_slider = alt.binding_range(min=-20, max=20, step=1)
    dx_var = alt.param(bind=dx_slider, value=5, name="dx")

    dy_slider = alt.binding_range(min=-20, max=20, step=1)
    dy_var = alt.param(bind=dy_slider, value=0, name="dy")

    xOffset_slider = alt.binding_range(min=-20, max=20, step=1)
    xOffset_var = alt.param(bind=xOffset_slider, value=0, name="xOffset")

    yOffset_slider = alt.binding_range(min=-20, max=20, step=1)
    yOffset_var = alt.param(bind=yOffset_slider, value=0, name="yOffset")

    fontSize_slider = alt.binding_range(min=1, max=36, step=1)
    fontSize_var = alt.param(bind=fontSize_slider, value=14, name="fontSize")

    limit_slider = alt.binding_range(min=0, max=150, step=1)
    limit_var = alt.param(bind=limit_slider, value=0, name="limit")

    align_select = alt.binding_select(options=["left", "center", "right"])
    align_var = alt.param(bind=align_select, value="left", name="align")

    baseline_select = alt.binding_select(options=["alphabetic", "top", "middle", "bottom"])
    baseline_var = alt.param(bind=baseline_select, value="middle", name="baseline")

    font_select = alt.binding_select(options=["sans-serif", "serif", "monospace"])
    font_var = alt.param(bind=font_select, value="sans-serif", name="font")

    fontWeight_select = alt.binding_select(options=["normal", "bold"])
    fontWeight_var = alt.param(bind=fontWeight_select, value="normal", name="fontWeight")

    fontStyle_select = alt.binding_select(options=["normal", "italic"])
    fontStyle_var = alt.param(bind=fontStyle_select, value="normal", name="fontStyle")

    source = pd.DataFrame(
        {
            "a": [30, 25, 70],
            "b": [28, 65, 43],
            "label": ["Andy", "Brian", "Charlie"],
        }
    )

    base = alt.Chart(source).encode(
        x=alt.X("a:Q").axis(labelAngle=0).scale(domain=[0, 100]),
        y=alt.Y("b:Q").scale(domain=[0, 100]),
    )

    pts = base.mark_point()

    text = base.mark_text(
        dx=dx_var,
        dy=dy_var,
        xOffset=xOffset_var,
        yOffset=yOffset_var,
        angle=angle_var,
        align=align_var,
        baseline=baseline_var,
        font=font_var,
        fontSize=fontSize_var,
        fontStyle=fontStyle_var,
        fontWeight=fontWeight_var,
        limit=limit_var,
    ).encode(text="label:N")

    (pts + text).add_params(
        dx_var,
        dy_var,
        xOffset_var,
        yOffset_var,
        angle_var,
        align_var,
        baseline_var,
        font_var,
        fontSize_var,
        fontStyle_var,
        fontWeight_var,
        limit_var,
    )


A ``text`` mark definition can contain any :ref:`standard mark properties <mark-properties>`
and the following special properties:

.. altair-object-table:: altair.MarkDef
   :properties: angle align baseline dir dx dy ellipsis font fontSize fontStyle fontWeight limit lineHeight radius text theta

Examples
--------
Text Table Heatmap
^^^^^^^^^^^^^^^^^^
.. altair-plot::
    import altair as alt
    from altair.datasets import data

    source = data.cars()

    base = alt.Chart(source).transform_aggregate(
        num_cars="count()",
        groupby=["Origin", "Cylinders"],
    ).encode(
        alt.X("Cylinders:O").scale(paddingInner=0),
        alt.Y("Origin:O").scale(paddingInner=0),
    )

    heatmap = base.mark_rect().encode(
        alt.Color("num_cars:Q")
            .scale(scheme="viridis")
            .legend(direction="horizontal")
    )

    predicate = alt.datum.num_cars > 100
    text = base.mark_text(baseline="middle").encode(
        text="num_cars:Q",
        color=alt.when(predicate).then(alt.value("black")).otherwise(alt.value("white")),
    )

    heatmap + text

Labels
^^^^^^
You can also use ``text`` marks as labels for other marks and set offset (``dx`` or ``dy``), ``align``, and ``baseline`` properties of the mark definition.

.. altair-plot::
    import altair as alt
    import pandas as pd

    source = pd.DataFrame({
        "a": ["A", "B", "C"],
        "b": [28, 55, 43]
    })

    bar = alt.Chart(source).mark_bar().encode(
        y="a:N",
        x=alt.X("b:Q").scale(domain=[0, 60])
    )
    text = bar.mark_text(
        align="left",
        baseline="middle",
        dx=3
    ).encode(text="b")

    bar + text

Labels Position Based on Condition
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
By default, text mark as labels in Altair are positioned above or to the right of the value. 
However, when dealing with negative values, this default positioning can lead to label overlap with the bar. 
To address this issue, you can set label positions via :ref:`expressions`. 
Here's an example demonstrating how to do this:

.. altair-plot::
    import altair as alt
    import pandas as pd

    source = pd.DataFrame({
        "a": ["A", "B", "C"],
        "b": [28, -5, 10]
    })

    bar = alt.Chart(source).mark_bar().encode(
        y="a:N",
        x=alt.X("b:Q").scale(domain=[-10, 35])
    )

    text_conditioned = bar.mark_text(
        align="left",
        baseline="middle",
        dx=alt.expr(alt.expr.if_(alt.datum.b >= 0, 10, -20))
    ).encode(text="b")

    bar + text_conditioned


Scatter Plot with Text
^^^^^^^^^^^^^^^^^^^^^^
Mapping a field to ``text`` channel of text mark sets the mark's text value. For example, we can make a colored scatter plot with text marks showing the initial character of its origin, instead of ``point`` marks.

.. altair-plot::
    import altair as alt
    from altair.datasets import data
    from altair import datum

    source = data.cars()

    alt.Chart(source).mark_text().encode(
        x="Horsepower:Q",
        y="Miles_per_Gallon:Q",
        color="Origin:N",
        text="Origin[0]:N",
    )

Geo Text
^^^^^^^^
By mapping geographic coordinate data to ``longitude`` and ``latitude`` channels of a corresponding projection, we can show text at accurate locations. The example below shows the name of every US state capital at the location of the capital.

.. altair-plot::
    import altair as alt
    from altair.datasets import data

    states = alt.topo_feature(data.us_10m.url, feature="states")

    source = data.us_state_capitals()

    background = alt.Chart(states).mark_geoshape(
        fill="lightgray",
        stroke="white",
    ).properties(
        width=750,
        height=500,
    ).project("albersUsa")

    line = alt.Chart(source).mark_text(dy=-10).encode(
        latitude="lat:Q",
        longitude="lon:Q",
        text="city:N"
    )

    point = alt.Chart(source).mark_circle().encode(
        latitude="lat:Q",
        longitude="lon:Q",
        color=alt.value("orange"),
    )

    background + line + point
