class ScatterBuilder(XYBuilder):
    """This is the Scatter class and it is in charge of plotting
    Scatter charts in an easy and intuitive way.

    Essentially, we provide a way to ingest the data, make the proper
    calculations and push the references into a source object.
    We additionally make calculations for the ranges. And finally add
    the needed glyphs (markers) taking the references from the source.

    """

    default_attributes = {'color': ColorAttr(),
                          'marker': MarkerAttr()}

    def yield_renderers(self):
        """Use the marker glyphs to display the points.

        Takes reference points from data loaded at the ColumnDataSource.
        """

        for group in self._data.groupby(**self.attributes):

            glyph = PointGlyph(label=group.label,
                               x=group.get_values(self.x.selection),
                               y=group.get_values(self.y.selection),
                               line_color=group['color'],
                               fill_color=group['color'],
                               marker=group['marker'])

            self.add_glyph(group, glyph)

            for renderer in glyph.renderers:
                yield renderer
class ChordBuilder(Builder):
    """ This is the Chord builder and it is in charge of plotting
    Chord graphs in an easy and intuitive way.

    Essentially, we provide a way to ingest the data, make the proper
    calculations and push the references into a source object.
    We additionally make calculations for the ranges. And finally add
    the needed glyphs (markers) taking the references from the source.

    """

    default_attributes = {'color': ColorAttr(),
                          'marker': MarkerAttr(),
                          'stack': CatAttr()}

    dimensions = ['values']

    values = Dimension('values')

    arcs_data = Instance(ColumnDataSource)
    text_data = Instance(ColumnDataSource)
    connection_data = Instance(ColumnDataSource)

    origin = String()
    destination = String()
    value = Any()
    square_matrix = Bool()
    label = Seq(Any())
    matrix = Array(Array(Either(Float(), Int())))

    def set_ranges(self):
        rng = 1.1 if not self.label else 1.8
        self.x_range = Range1d(-rng, rng)
        self.y_range = Range1d(-rng, rng)

    def setup(self):

        # Process only if not a square_matrix
        if not self.square_matrix:
            source = self.values._data[self.origin]
            target = self.values._data[self.destination]
            union = source.append(target).unique()
            N = union.shape[0]
            m = pd.DataFrame(np.zeros((N, N)), columns=union, index=union)

            if not self.label:
                self.label = list(union)

            if self.value is None:
                for _, row in self.values._data.iterrows():
                    m[row[self.origin]][row[self.destination]] += 1
                self.matrix = m.get_values()

            if self.value is not None:

                if isinstance(self.value, int) or isinstance(self.value, float):
                    for _, row in self.values._data.iterrows():
                        m[row[self.origin]][row[self.destination]] = self.value
                    self.matrix = m.get_values()

                elif isinstance(self.value, str):
                    for _, row in self.values._data.iterrows():
                        m[row[self.origin]][row[self.destination]] = row[self.value]
                    self.matrix = m.get_values().T
        else:
            # It's already a square matrix
            self.matrix = self._data.df.get_values()

        if self.label:
            assert len(self.label) == self.matrix.shape[0]

    def process_data(self):

        weights_of_areas = (self.matrix.sum(axis=0) + self.matrix.sum(axis=1)) - self.matrix.diagonal()
        areas_in_radians = (weights_of_areas / weights_of_areas.sum()) * (2 * pi)

        # We add a zero in the begging for the cumulative sum
        points = np.zeros((areas_in_radians.shape[0] + 1))
        points[1:] = areas_in_radians
        points = points.cumsum()

        colors = [color_in_equal_space(area / areas_in_radians.shape[0]) for area in range(areas_in_radians.shape[0])]

        arcs_data = pd.DataFrame({
            'start_angle': points[:-1],
            'end_angle': points[1:],
            'line_color': colors
        })

        self.arcs_data = ColumnDataSource(arcs_data)

        # Text
        if self.label:
            text_radius = 1.1
            angles = (points[:-1]+points[1:])/2.0

            text_positions = pd.DataFrame({
                'angles': angles,
                'text_x': np.cos(angles) * text_radius,
                'text_y': np.sin(angles) * text_radius,
                'text': list(self.label)
            })

            self.text_data = ColumnDataSource(text_positions)

        # Lines

        all_areas = []
        for i in range(areas_in_radians.shape[0]):
            all_areas.append(Area(weights_of_areas[i], points[:-1][i], points[1:][i]))

        all_connections = []
        for j, region1 in enumerate(self.matrix):
            # Get the connections origin region
            source = all_areas[j]
            color = colors[j]
            weight = weights_of_areas[j]

            for k, region2 in enumerate(region1):
                # Get the connection destination region
                target = all_areas[k]
                for _ in range(int(region2)):
                    p1 = source.free_points.pop()
                    p2 = target.free_points.pop()
                    # Get both regions free points and create a connection with the data
                    all_connections.append(p1 + p2 + [color, weight])

        connections_df = pd.DataFrame(all_connections, dtype=str)
        connections_df.columns = ["start_x", "start_y", "end_x", "end_y", "colors", "weight"]
        connections_df["cx0"] = connections_df.start_x.astype("float64")/2
        connections_df["cy0"] = connections_df.start_y.astype("float64")/2
        connections_df["cx1"] = connections_df.end_x.astype("float64")/2
        connections_df["cy1"] = connections_df.end_y.astype("float64")/2
        connections_df.weight = (connections_df.weight.astype("float64")/connections_df.weight.astype("float64").sum()) * 3000

        self.connection_data = ColumnDataSource(connections_df)

    def yield_renderers(self):
        """Use the marker glyphs to display the arcs and beziers.
        Takes reference points from data loaded at the ColumnDataSource.
        """
        beziers = Bezier(x0='start_x',
                         y0='start_y',
                         x1='end_x',
                         y1='end_y',
                         cx0='cx0',
                         cy0='cy0',
                         cx1='cx1',
                         cy1='cy1',
                         line_alpha='weight',
                         line_color='colors')

        yield GlyphRenderer(data_source=self.connection_data, glyph=beziers)

        arcs = Arc(x=0,
                   y=0,
                   radius=1,
                   line_width=10,
                   start_angle='start_angle',
                   end_angle='end_angle',
                   line_color='line_color')

        yield GlyphRenderer(data_source=self.arcs_data, glyph=arcs)

        if self.label:

            text_props = {
                "text_color": "#000000",
                "text_font_size": "8pt",
                "text_align": "left",
                "text_baseline": "middle"
            }

            labels = Text(x='text_x',
                          y='text_y',
                          text='text',
                          angle='angles',
                          **text_props
                          )

            yield GlyphRenderer(data_source=self.text_data, glyph=labels)