# -*- coding: utf-8 -*-
# This file is part of pygal
#
# A python svg graph plotting library
# Copyright © 2012-2025 Kozea
#
# This library is free software: you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option) any
# later version.
#
# This library is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with pygal. If not, see <http://www.gnu.org/licenses/>.
"""
Dot chart displaying values as a grid of dots, the bigger the value
the bigger the dot
"""

from math import log10

from pygal.graph.graph import Graph
from pygal.util import alter, cached_property, decorate, safe_enumerate
from pygal.view import ReverseView, View


class Dot(Graph):
    """Dot graph class"""

    def dot(self, serie, r_max):
        """Draw a dot line"""
        serie_node = self.svg.serie(serie)
        view_values = list(map(self.view, serie.points))
        for i, value in safe_enumerate(serie.values):
            x, y = view_values[i]

            if self.logarithmic:
                log10min = log10(self._min) - 1
                log10max = log10(self._max or 1)

                if value != 0:
                    size = r_max * ((log10(abs(value)) - log10min) /
                                    (log10max - log10min))
                else:
                    size = 0
            else:
                size = r_max * (abs(value) / (self._max or 1))

            metadata = serie.metadata.get(i)
            dots = decorate(
                self.svg, self.svg.node(serie_node['plot'], class_="dots"),
                metadata
            )
            alter(
                self.svg.node(
                    dots,
                    'circle',
                    cx=x,
                    cy=y,
                    r=size,
                    class_='dot reactive tooltip-trigger' +
                    (' negative' if value < 0 else '')
                ), metadata
            )

            val = self._format(serie, i)
            self._tooltip_data(
                dots, val, x, y, 'centered', self._get_x_label(i)
            )
            self._static_value(serie_node, val, x, y, metadata)

    def _compute(self):
        """Compute y min and max and y scale and set labels"""
        x_len = self._len
        y_len = self._order
        self._box.xmax = x_len
        self._box.ymax = y_len

        self._x_pos = [n / 2 for n in range(1, 2 * x_len, 2)]
        self._y_pos = [n / 2 for n in reversed(range(1, 2 * y_len, 2))]

        for j, serie in enumerate(self.series):
            serie.points = [(self._x_pos[i], self._y_pos[j])
                            for i in range(x_len)]

    def _compute_y_labels(self):
        if self.y_labels:
            y_labels = [str(label) for label in self.y_labels]
        else:
            y_labels = [
                (
                    serie.title['title']
                    if isinstance(serie.title, dict)
                    else serie.title
                ) or ''
                for serie in self.series
            ]
        self._y_labels = list(zip(y_labels, self._y_pos))

    def _set_view(self):
        """Assign a view to current graph"""
        view_class = ReverseView if self.inverse_y_axis else View

        self.view = view_class(
            self.width - self.margin_box.x, self.height - self.margin_box.y,
            self._box
        )

    @cached_property
    def _values(self):
        """Getter for series values (flattened)"""
        return [abs(val) for val in super(Dot, self)._values if val != 0]

    @cached_property
    def _max(self):
        """Getter for the maximum series value"""
        return (
            self.range[1] if (self.range and self.range[1] is not None) else
            (max(map(abs, self._values)) if self._values else None)
        )

    def _plot(self):
        """Plot all dots for series"""
        r_max = min(
            self.view.x(1) - self.view.x(0),
            (self.view.y(0) or 0) - self.view.y(1)
        ) / (2 * 1.05)
        for serie in self.series:
            self.dot(serie, r_max)
