We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
changing TextPlot color & font
Hello,

I would like to change TextPlot color & font.

Initialization is ok, but when the color trait is changed for instance, the color is not changed in the display.

There is the same issue with the font trait.

Here's the CME.

```python
#! /usr/bin/env python3

import numpy as np

from chaco.api import ArrayPlotData, Plot
from enable.api import ComponentEditor
from traits.api import (
    Array,
    Color,
    Font,
    HasTraits,
    Instance,
    List
)
from traitsui.api import Item, UItem, View


class Data(HasTraits):

    data = Array
    labels = List
    color = Color
    font = Font
    plot = Instance(Plot)

    def _data_default(self):
        data = np.array([[0, 0], [0, 1], [1, 1], [1, 0]])
        return (data)

    def _labels_default(self):
        labels = ['A', 'B', 'C', 'D']
        return (labels)

    def _plot_default(self):
        self.plotdata = ArrayPlotData()
        self.plotdata.set_data('x', self.data[:, 0])
        self.plotdata.set_data('y', self.data[:, 1])
        self.plotdata.set_data('labels', self.labels)

        plot = Plot(self.plotdata)
        plot.range2d.set_bounds((-1, -1), (2, 2))
        plot.plot(("x", "y"),
                  type='scatter',
                  marker='dot',
                  color=self.color)
        plot.plot(("x", "y", "labels"),
                  type='text',
                  text_margin=4,
                  h_position='right',
                  text_offset=(4, 4),
                  text_color=self.color)

        return plot

    traits_view = View(
        UItem(
            "plot",
            editor=ComponentEditor(),
            resizable=True
        ),
        UItem('color',
              style='simple'),
        UItem('font'),
        resizable=True,
        buttons=["OK"],
        width=900,
        height=800,
    )

    def _color_changed(self):
        self.plot.plots['plot0'][0].color = self.color
        self.plot.plots['plot1'][0].text_color = self.color

    def _font_changed(self):
        name = self.font.family()
        size = self.font.pointSize()
        self.plot.plots['plot1'][0].text_font = '%s %d' % (name, size)


if __name__ == '__main__':
    viewer = Data(color=(255, 128, 64))
    viewer.configure_traits()
```

Thanks in advance for any help.

Regards

Debian 10 x86_64, Python 3.7.3, ETS source code from git repo


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `chaco/plots/text_plot.py`
Content:
```
1 # (C) Copyright 2005-2021 Enthought, Inc., Austin, TX
2 # All rights reserved.
3 #
4 # This software is provided without warranty under the terms of the BSD
5 # license included in LICENSE.txt and may be redistributed only under
6 # the conditions described in the aforementioned license. The license
7 # is also available online at http://www.enthought.com/licenses/BSD.txt
8 #
9 # Thanks for using Enthought open source!
10 
11 """
12 A plot that renders text values in two dimensions
13 
14 """
15 
16 
17 from numpy import array, column_stack, empty, isfinite
18 
19 # Enthought library imports
20 from enable.api import black_color_trait
21 from kiva.trait_defs.kiva_font_trait import KivaFont
22 from traits.api import Bool, Enum, Float, Int, Instance, List, Tuple, observe
23 
24 # local imports
25 from chaco.array_data_source import ArrayDataSource
26 from chaco.label import Label
27 from chaco.base_xy_plot import BaseXYPlot
28 
29 
30 class TextPlot(BaseXYPlot):
31     """ A plot that positions textual labels in 2D """
32 
33     #: text values corresponding to indices
34     text = Instance(ArrayDataSource)
35 
36     #: The font of the tick labels.
37     text_font = KivaFont("sans-serif 10")
38 
39     #: The color of the tick labels.
40     text_color = black_color_trait
41 
42     #: The rotation of the tick labels.
43     text_rotate_angle = Float(0)
44 
45     #: The margin around the label.
46     text_margin = Int(2)
47 
48     #: horizontal position of text relative to target point
49     h_position = Enum("center", "left", "right")
50 
51     #: vertical position of text relative to target point
52     v_position = Enum("center", "top", "bottom")
53 
54     #: offset of text relative to non-index direction in pixels
55     text_offset = Tuple(Float, Float)
56 
57     # ------------------------------------------------------------------------
58     # Private traits
59     # ------------------------------------------------------------------------
60 
61     #: flag for whether the cache of Label instances is valid
62     _label_cache_valid = Bool(False, transient=True)
63 
64     #: cache of Label instances for faster rendering
65     _label_cache = List(transient=True)
66 
67     #: cache of bounding boxes of labels
68     _label_box_cache = List(transient=True)
69 
70     # ------------------------------------------------------------------------
71     # Private methods
72     # ------------------------------------------------------------------------
73 
74     def _compute_labels(self, gc):
75         """Generate the Label instances for the plot. """
76         self._label_cache = [
77             Label(
78                 text=text,
79                 font=self.text_font,
80                 color=self.text_color,
81                 rotate_angle=self.text_rotate_angle,
82                 margin=self.text_margin,
83             )
84             for text in self.text.get_data()
85         ]
86         self._label_box_cache = [
87             array(label.get_bounding_box(gc), float)
88             for label in self._label_cache
89         ]
90         self._label_cache_valid = True
91 
92     def _gather_points(self):
93         """Abstract method to collect data points that are within the range of
94         the plot, and cache them.
95         """
96         if self._cache_valid:
97             return
98 
99         if not self.index or not self.value:
100             return
101 
102         index, index_mask = self.index.get_data_mask()
103         value, value_mask = self.value.get_data_mask()
104 
105         if len(index) == 0 or len(value) == 0 or len(index) != len(value):
106             self._cached_data_pts = []
107             self._cached_point_mask = []
108             self._cache_valid = True
109             return
110 
111         index_range_mask = self.index_mapper.range.mask_data(index)
112         value_range_mask = self.value_mapper.range.mask_data(value)
113 
114         nan_mask = isfinite(index) & index_mask & isfinite(value) & value_mask
115         point_mask = nan_mask & index_range_mask & value_range_mask
116 
117         if not self._cache_valid:
118             if not point_mask.all():
119                 points = column_stack([index[point_mask], value[point_mask]])
120             else:
121                 points = column_stack([index, value])
122             self._cached_data_pts = points
123             self._cached_point_mask = point_mask
124             self._cache_valid = True
125 
126     def _render(self, gc, pts):
127         if not self._label_cache_valid:
128             self._compute_labels(gc)
129 
130         labels = [
131             label
132             for label, mask in zip(self._label_cache, self._cached_point_mask)
133             if mask
134         ]
135         boxes = [
136             label
137             for label, mask in zip(
138                 self._label_box_cache, self._cached_point_mask
139             )
140             if mask
141         ]
142         offset = empty((2,), float)
143 
144         with gc:
145             gc.clip_to_rect(self.x, self.y, self.width, self.height)
146             for pt, label, box in zip(pts, labels, boxes):
147                 with gc:
148                     if self.h_position == "center":
149                         offset[0] = -box[0] / 2 + self.text_offset[0]
150                     elif self.h_position == "right":
151                         offset[0] = self.text_offset[0]
152                     elif self.h_position == "left":
153                         offset[0] = -box[0] / 2 + self.text_offset[0]
154                     if self.v_position == "center":
155                         offset[1] = -box[1] / 2 + self.text_offset[1]
156                     elif self.v_position == "top":
157                         offset[1] = self.text_offset[1]
158                     elif self.v_position == "bottom":
159                         offset[1] = -box[1] / 2 - self.text_offset[1]
160 
161                     pt += offset
162                     gc.translate_ctm(*pt)
163 
164                     label.draw(gc)
165 
166     # ------------------------------------------------------------------------
167     # Trait events
168     # ------------------------------------------------------------------------
169 
170     @observe("index.data_changed")
171     def _invalidate(self, event):
172         self._cache_valid = False
173         self._screen_cache_valid = False
174         self._label_cache_valid = False
175 
176     @observe("value.data_changed")
177     def _invalidate_labels(self, event):
178         self._label_cache_valid = False
179
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/chaco/plots/text_plot.py b/chaco/plots/text_plot.py
--- a/chaco/plots/text_plot.py
+++ b/chaco/plots/text_plot.py
@@ -34,25 +34,25 @@
     text = Instance(ArrayDataSource)
 
     #: The font of the tick labels.
-    text_font = KivaFont("sans-serif 10")
+    text_font = KivaFont("sans-serif 10", redraw=True)
 
     #: The color of the tick labels.
-    text_color = black_color_trait
+    text_color = black_color_trait(redraw=True)
 
     #: The rotation of the tick labels.
-    text_rotate_angle = Float(0)
+    text_rotate_angle = Float(0, redraw=True)
 
     #: The margin around the label.
-    text_margin = Int(2)
+    text_margin = Int(2, redraw=True)
 
     #: horizontal position of text relative to target point
-    h_position = Enum("center", "left", "right")
+    h_position = Enum("center", "left", "right", redraw=True)
 
     #: vertical position of text relative to target point
-    v_position = Enum("center", "top", "bottom")
+    v_position = Enum("center", "top", "bottom", redraw=True)
 
     #: offset of text relative to non-index direction in pixels
-    text_offset = Tuple(Float, Float)
+    text_offset = Tuple(Float, Float, redraw=True)
 
     # ------------------------------------------------------------------------
     # Private traits
@@ -173,6 +173,6 @@
         self._screen_cache_valid = False
         self._label_cache_valid = False
 
-    @observe("value.data_changed")
+    @observe("value.data_changed,+redraw")
     def _invalidate_labels(self, event):
         self._label_cache_valid = False

{"golden_diff": "diff --git a/chaco/plots/text_plot.py b/chaco/plots/text_plot.py\n--- a/chaco/plots/text_plot.py\n+++ b/chaco/plots/text_plot.py\n@@ -34,25 +34,25 @@\n     text = Instance(ArrayDataSource)\n \n     #: The font of the tick labels.\n-    text_font = KivaFont(\"sans-serif 10\")\n+    text_font = KivaFont(\"sans-serif 10\", redraw=True)\n \n     #: The color of the tick labels.\n-    text_color = black_color_trait\n+    text_color = black_color_trait(redraw=True)\n \n     #: The rotation of the tick labels.\n-    text_rotate_angle = Float(0)\n+    text_rotate_angle = Float(0, redraw=True)\n \n     #: The margin around the label.\n-    text_margin = Int(2)\n+    text_margin = Int(2, redraw=True)\n \n     #: horizontal position of text relative to target point\n-    h_position = Enum(\"center\", \"left\", \"right\")\n+    h_position = Enum(\"center\", \"left\", \"right\", redraw=True)\n \n     #: vertical position of text relative to target point\n-    v_position = Enum(\"center\", \"top\", \"bottom\")\n+    v_position = Enum(\"center\", \"top\", \"bottom\", redraw=True)\n \n     #: offset of text relative to non-index direction in pixels\n-    text_offset = Tuple(Float, Float)\n+    text_offset = Tuple(Float, Float, redraw=True)\n \n     # ------------------------------------------------------------------------\n     # Private traits\n@@ -173,6 +173,6 @@\n         self._screen_cache_valid = False\n         self._label_cache_valid = False\n \n-    @observe(\"value.data_changed\")\n+    @observe(\"value.data_changed,+redraw\")\n     def _invalidate_labels(self, event):\n         self._label_cache_valid = False\n", "issue": "changing TextPlot color & font\nHello,\r\n\r\nI would like to change TextPlot color & font.\r\n\r\nInitialization is ok, but when the color trait is changed for instance, the color is not changed in the display.\r\n\r\nThere is the same issue with the font trait.\r\n\r\nHere's the CME.\r\n\r\n```python\r\n#! /usr/bin/env python3\r\n\r\nimport numpy as np\r\n\r\nfrom chaco.api import ArrayPlotData, Plot\r\nfrom enable.api import ComponentEditor\r\nfrom traits.api import (\r\n    Array,\r\n    Color,\r\n    Font,\r\n    HasTraits,\r\n    Instance,\r\n    List\r\n)\r\nfrom traitsui.api import Item, UItem, View\r\n\r\n\r\nclass Data(HasTraits):\r\n\r\n    data = Array\r\n    labels = List\r\n    color = Color\r\n    font = Font\r\n    plot = Instance(Plot)\r\n\r\n    def _data_default(self):\r\n        data = np.array([[0, 0], [0, 1], [1, 1], [1, 0]])\r\n        return (data)\r\n\r\n    def _labels_default(self):\r\n        labels = ['A', 'B', 'C', 'D']\r\n        return (labels)\r\n\r\n    def _plot_default(self):\r\n        self.plotdata = ArrayPlotData()\r\n        self.plotdata.set_data('x', self.data[:, 0])\r\n        self.plotdata.set_data('y', self.data[:, 1])\r\n        self.plotdata.set_data('labels', self.labels)\r\n\r\n        plot = Plot(self.plotdata)\r\n        plot.range2d.set_bounds((-1, -1), (2, 2))\r\n        plot.plot((\"x\", \"y\"),\r\n                  type='scatter',\r\n                  marker='dot',\r\n                  color=self.color)\r\n        plot.plot((\"x\", \"y\", \"labels\"),\r\n                  type='text',\r\n                  text_margin=4,\r\n                  h_position='right',\r\n                  text_offset=(4, 4),\r\n                  text_color=self.color)\r\n\r\n        return plot\r\n\r\n    traits_view = View(\r\n        UItem(\r\n            \"plot\",\r\n            editor=ComponentEditor(),\r\n            resizable=True\r\n        ),\r\n        UItem('color',\r\n              style='simple'),\r\n        UItem('font'),\r\n        resizable=True,\r\n        buttons=[\"OK\"],\r\n        width=900,\r\n        height=800,\r\n    )\r\n\r\n    def _color_changed(self):\r\n        self.plot.plots['plot0'][0].color = self.color\r\n        self.plot.plots['plot1'][0].text_color = self.color\r\n\r\n    def _font_changed(self):\r\n        name = self.font.family()\r\n        size = self.font.pointSize()\r\n        self.plot.plots['plot1'][0].text_font = '%s %d' % (name, size)\r\n\r\n\r\nif __name__ == '__main__':\r\n    viewer = Data(color=(255, 128, 64))\r\n    viewer.configure_traits()\r\n```\r\n\r\nThanks in advance for any help.\r\n\r\nRegards\r\n\r\nDebian 10 x86_64, Python 3.7.3, ETS source code from git repo\r\n\n", "before_files": [{"content": "# (C) Copyright 2005-2021 Enthought, Inc., Austin, TX\n# All rights reserved.\n#\n# This software is provided without warranty under the terms of the BSD\n# license included in LICENSE.txt and may be redistributed only under\n# the conditions described in the aforementioned license. The license\n# is also available online at http://www.enthought.com/licenses/BSD.txt\n#\n# Thanks for using Enthought open source!\n\n\"\"\"\nA plot that renders text values in two dimensions\n\n\"\"\"\n\n\nfrom numpy import array, column_stack, empty, isfinite\n\n# Enthought library imports\nfrom enable.api import black_color_trait\nfrom kiva.trait_defs.kiva_font_trait import KivaFont\nfrom traits.api import Bool, Enum, Float, Int, Instance, List, Tuple, observe\n\n# local imports\nfrom chaco.array_data_source import ArrayDataSource\nfrom chaco.label import Label\nfrom chaco.base_xy_plot import BaseXYPlot\n\n\nclass TextPlot(BaseXYPlot):\n    \"\"\" A plot that positions textual labels in 2D \"\"\"\n\n    #: text values corresponding to indices\n    text = Instance(ArrayDataSource)\n\n    #: The font of the tick labels.\n    text_font = KivaFont(\"sans-serif 10\")\n\n    #: The color of the tick labels.\n    text_color = black_color_trait\n\n    #: The rotation of the tick labels.\n    text_rotate_angle = Float(0)\n\n    #: The margin around the label.\n    text_margin = Int(2)\n\n    #: horizontal position of text relative to target point\n    h_position = Enum(\"center\", \"left\", \"right\")\n\n    #: vertical position of text relative to target point\n    v_position = Enum(\"center\", \"top\", \"bottom\")\n\n    #: offset of text relative to non-index direction in pixels\n    text_offset = Tuple(Float, Float)\n\n    # ------------------------------------------------------------------------\n    # Private traits\n    # ------------------------------------------------------------------------\n\n    #: flag for whether the cache of Label instances is valid\n    _label_cache_valid = Bool(False, transient=True)\n\n    #: cache of Label instances for faster rendering\n    _label_cache = List(transient=True)\n\n    #: cache of bounding boxes of labels\n    _label_box_cache = List(transient=True)\n\n    # ------------------------------------------------------------------------\n    # Private methods\n    # ------------------------------------------------------------------------\n\n    def _compute_labels(self, gc):\n        \"\"\"Generate the Label instances for the plot. \"\"\"\n        self._label_cache = [\n            Label(\n                text=text,\n                font=self.text_font,\n                color=self.text_color,\n                rotate_angle=self.text_rotate_angle,\n                margin=self.text_margin,\n            )\n            for text in self.text.get_data()\n        ]\n        self._label_box_cache = [\n            array(label.get_bounding_box(gc), float)\n            for label in self._label_cache\n        ]\n        self._label_cache_valid = True\n\n    def _gather_points(self):\n        \"\"\"Abstract method to collect data points that are within the range of\n        the plot, and cache them.\n        \"\"\"\n        if self._cache_valid:\n            return\n\n        if not self.index or not self.value:\n            return\n\n        index, index_mask = self.index.get_data_mask()\n        value, value_mask = self.value.get_data_mask()\n\n        if len(index) == 0 or len(value) == 0 or len(index) != len(value):\n            self._cached_data_pts = []\n            self._cached_point_mask = []\n            self._cache_valid = True\n            return\n\n        index_range_mask = self.index_mapper.range.mask_data(index)\n        value_range_mask = self.value_mapper.range.mask_data(value)\n\n        nan_mask = isfinite(index) & index_mask & isfinite(value) & value_mask\n        point_mask = nan_mask & index_range_mask & value_range_mask\n\n        if not self._cache_valid:\n            if not point_mask.all():\n                points = column_stack([index[point_mask], value[point_mask]])\n            else:\n                points = column_stack([index, value])\n            self._cached_data_pts = points\n            self._cached_point_mask = point_mask\n            self._cache_valid = True\n\n    def _render(self, gc, pts):\n        if not self._label_cache_valid:\n            self._compute_labels(gc)\n\n        labels = [\n            label\n            for label, mask in zip(self._label_cache, self._cached_point_mask)\n            if mask\n        ]\n        boxes = [\n            label\n            for label, mask in zip(\n                self._label_box_cache, self._cached_point_mask\n            )\n            if mask\n        ]\n        offset = empty((2,), float)\n\n        with gc:\n            gc.clip_to_rect(self.x, self.y, self.width, self.height)\n            for pt, label, box in zip(pts, labels, boxes):\n                with gc:\n                    if self.h_position == \"center\":\n                        offset[0] = -box[0] / 2 + self.text_offset[0]\n                    elif self.h_position == \"right\":\n                        offset[0] = self.text_offset[0]\n                    elif self.h_position == \"left\":\n                        offset[0] = -box[0] / 2 + self.text_offset[0]\n                    if self.v_position == \"center\":\n                        offset[1] = -box[1] / 2 + self.text_offset[1]\n                    elif self.v_position == \"top\":\n                        offset[1] = self.text_offset[1]\n                    elif self.v_position == \"bottom\":\n                        offset[1] = -box[1] / 2 - self.text_offset[1]\n\n                    pt += offset\n                    gc.translate_ctm(*pt)\n\n                    label.draw(gc)\n\n    # ------------------------------------------------------------------------\n    # Trait events\n    # ------------------------------------------------------------------------\n\n    @observe(\"index.data_changed\")\n    def _invalidate(self, event):\n        self._cache_valid = False\n        self._screen_cache_valid = False\n        self._label_cache_valid = False\n\n    @observe(\"value.data_changed\")\n    def _invalidate_labels(self, event):\n        self._label_cache_valid = False\n", "path": "chaco/plots/text_plot.py"}], "after_files": [{"content": "# (C) Copyright 2005-2021 Enthought, Inc., Austin, TX\n# All rights reserved.\n#\n# This software is provided without warranty under the terms of the BSD\n# license included in LICENSE.txt and may be redistributed only under\n# the conditions described in the aforementioned license. The license\n# is also available online at http://www.enthought.com/licenses/BSD.txt\n#\n# Thanks for using Enthought open source!\n\n\"\"\"\nA plot that renders text values in two dimensions\n\n\"\"\"\n\n\nfrom numpy import array, column_stack, empty, isfinite\n\n# Enthought library imports\nfrom enable.api import black_color_trait\nfrom kiva.trait_defs.kiva_font_trait import KivaFont\nfrom traits.api import Bool, Enum, Float, Int, Instance, List, Tuple, observe\n\n# local imports\nfrom chaco.array_data_source import ArrayDataSource\nfrom chaco.label import Label\nfrom chaco.base_xy_plot import BaseXYPlot\n\n\nclass TextPlot(BaseXYPlot):\n    \"\"\" A plot that positions textual labels in 2D \"\"\"\n\n    #: text values corresponding to indices\n    text = Instance(ArrayDataSource)\n\n    #: The font of the tick labels.\n    text_font = KivaFont(\"sans-serif 10\", redraw=True)\n\n    #: The color of the tick labels.\n    text_color = black_color_trait(redraw=True)\n\n    #: The rotation of the tick labels.\n    text_rotate_angle = Float(0, redraw=True)\n\n    #: The margin around the label.\n    text_margin = Int(2, redraw=True)\n\n    #: horizontal position of text relative to target point\n    h_position = Enum(\"center\", \"left\", \"right\", redraw=True)\n\n    #: vertical position of text relative to target point\n    v_position = Enum(\"center\", \"top\", \"bottom\", redraw=True)\n\n    #: offset of text relative to non-index direction in pixels\n    text_offset = Tuple(Float, Float, redraw=True)\n\n    # ------------------------------------------------------------------------\n    # Private traits\n    # ------------------------------------------------------------------------\n\n    #: flag for whether the cache of Label instances is valid\n    _label_cache_valid = Bool(False, transient=True)\n\n    #: cache of Label instances for faster rendering\n    _label_cache = List(transient=True)\n\n    #: cache of bounding boxes of labels\n    _label_box_cache = List(transient=True)\n\n    # ------------------------------------------------------------------------\n    # Private methods\n    # ------------------------------------------------------------------------\n\n    def _compute_labels(self, gc):\n        \"\"\"Generate the Label instances for the plot. \"\"\"\n        self._label_cache = [\n            Label(\n                text=text,\n                font=self.text_font,\n                color=self.text_color,\n                rotate_angle=self.text_rotate_angle,\n                margin=self.text_margin,\n            )\n            for text in self.text.get_data()\n        ]\n        self._label_box_cache = [\n            array(label.get_bounding_box(gc), float)\n            for label in self._label_cache\n        ]\n        self._label_cache_valid = True\n\n    def _gather_points(self):\n        \"\"\"Abstract method to collect data points that are within the range of\n        the plot, and cache them.\n        \"\"\"\n        if self._cache_valid:\n            return\n\n        if not self.index or not self.value:\n            return\n\n        index, index_mask = self.index.get_data_mask()\n        value, value_mask = self.value.get_data_mask()\n\n        if len(index) == 0 or len(value) == 0 or len(index) != len(value):\n            self._cached_data_pts = []\n            self._cached_point_mask = []\n            self._cache_valid = True\n            return\n\n        index_range_mask = self.index_mapper.range.mask_data(index)\n        value_range_mask = self.value_mapper.range.mask_data(value)\n\n        nan_mask = isfinite(index) & index_mask & isfinite(value) & value_mask\n        point_mask = nan_mask & index_range_mask & value_range_mask\n\n        if not self._cache_valid:\n            if not point_mask.all():\n                points = column_stack([index[point_mask], value[point_mask]])\n            else:\n                points = column_stack([index, value])\n            self._cached_data_pts = points\n            self._cached_point_mask = point_mask\n            self._cache_valid = True\n\n    def _render(self, gc, pts):\n        if not self._label_cache_valid:\n            self._compute_labels(gc)\n\n        labels = [\n            label\n            for label, mask in zip(self._label_cache, self._cached_point_mask)\n            if mask\n        ]\n        boxes = [\n            label\n            for label, mask in zip(\n                self._label_box_cache, self._cached_point_mask\n            )\n            if mask\n        ]\n        offset = empty((2,), float)\n\n        with gc:\n            gc.clip_to_rect(self.x, self.y, self.width, self.height)\n            for pt, label, box in zip(pts, labels, boxes):\n                with gc:\n                    if self.h_position == \"center\":\n                        offset[0] = -box[0] / 2 + self.text_offset[0]\n                    elif self.h_position == \"right\":\n                        offset[0] = self.text_offset[0]\n                    elif self.h_position == \"left\":\n                        offset[0] = -box[0] / 2 + self.text_offset[0]\n                    if self.v_position == \"center\":\n                        offset[1] = -box[1] / 2 + self.text_offset[1]\n                    elif self.v_position == \"top\":\n                        offset[1] = self.text_offset[1]\n                    elif self.v_position == \"bottom\":\n                        offset[1] = -box[1] / 2 - self.text_offset[1]\n\n                    pt += offset\n                    gc.translate_ctm(*pt)\n\n                    label.draw(gc)\n\n    # ------------------------------------------------------------------------\n    # Trait events\n    # ------------------------------------------------------------------------\n\n    @observe(\"index.data_changed\")\n    def _invalidate(self, event):\n        self._cache_valid = False\n        self._screen_cache_valid = False\n        self._label_cache_valid = False\n\n    @observe(\"value.data_changed,+redraw\")\n    def _invalidate_labels(self, event):\n        self._label_cache_valid = False\n", "path": "chaco/plots/text_plot.py"}]}