Additional statistic for flags

Hello,

I am learning Japanese and flag all words where I know all kanji as purple, all words that don’t contain kanji as blue and the rest are unflagged. I would really like to add another pie diagram to the statistics of the deck that would have unseen, seen but unflagged, purple and blue as metrics. I tried to include a new config file into a new folder in addons21, but then startup fails.

Code of the new config file:

from aqt import mw
from aqt.gui_hooks import stats_did_load

def get_counts(did):
db = mw.col.db

new_count = db.scalar(
    "SELECT COUNT() FROM cards WHERE did=? AND type=0 AND flags=0", did
) or 0

flag1 = db.scalar(
    "SELECT COUNT() FROM cards WHERE did=? AND flags=1", did
) or 0

flag2 = db.scalar(
    "SELECT COUNT() FROM cards WHERE did=? AND flags=2", did
) or 0

flag3 = db.scalar(
    "SELECT COUNT() FROM cards WHERE did=? AND flags=3", did
) or 0

flag4 = db.scalar(
    "SELECT COUNT() FROM cards WHERE did=? AND flags=4", did
) or 0

return new_count, flag1, flag2, flag3, flag4

def on_stats_loaded(web_content, context):

if not hasattr(context, “deck_id”):
return

did = context.deck_id

new_count, f1, f2, f3, f4 = get_counts(did)

html = f"""
<h2>Custom Flag Pie Chart</h2>
<canvas id="flagChart" width="300" height="300"></canvas>

<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
const data = {{
    labels: ["Not Seen", "Flag 1", "Flag 2", "Flag 3", "Flag 4"],
    datasets: [{{
        data: [{new_count}, {f1}, {f2}, {f3}, {f4}],
        backgroundColor: ["gray", "red", "orange", "green", "blue"]
    }}]
}};

new Chart(document.getElementById("flagChart"), {{
    type: "pie",
    data: data
}});
</script>
"""

web_content.body += html

stats_did_load.append(on_stats_loaded)

Does the addon Search Stats Extended not have this feature?

Go to Tools > Extensions > View Files > create a folder with any name, for example: Japanese
Inside it, create a file named __init__.py

Note: the format must be .py
Open it with Notepad, put the code below inside it and save it. In Anki, go to Tools > Japanese stats

from aqt import mw
from aqt.qt import *
from aqt.utils import showInfo

class PieChartWidget(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.data = [] # List: (Name, Value, Hex_Color)
        self.setMinimumSize(300, 300)

    def set_data(self, data):
        """Updates chart data and triggers a repaint"""
        self.data = data
        self.update() 

    def paintEvent(self, event):
        painter = QPainter(self)
        painter.setRenderHint(QPainter.RenderHint.Antialiasing)

        rect = QRectF(10, 10, self.width() - 20, self.height() - 20)
        
        total = sum(item[1] for item in self.data)
        
        if total == 0:
            painter.drawText(rect, Qt.AlignmentFlag.AlignCenter, "No cards found in this selection.")
            return

        start_angle = 0
        for name, value, color_hex in self.data:
            if value == 0:
                continue
            
            # QPainter uses 1/16th of a degree. 360 degrees = 5760
            span_angle = int((value / total) * 5760)
            
            color = QColor(color_hex)
            painter.setBrush(QBrush(color))
            painter.setPen(QPen(Qt.GlobalColor.white, 1))
            
            painter.drawPie(rect, start_angle, span_angle)
            start_angle += span_angle

class FlagStatsDialog(QDialog):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setWindowTitle("Japanese Stats (Flags)")
        self.resize(750, 500)

        # Allows minimizing/maximizing
        self.setWindowFlags(Qt.WindowType.Window)
        
        # Categories requested by the user
        self.categories = [
            {"name": "Known (Purple Flag)", "query": "flag:7", "color": "#9c27b0"},
            {"name": "No Kanji (Blue Flag)", "query": "flag:4", "color": "#4a90e2"},
            {"name": "Unseen (No Flag)", "query": "flag:0 is:new", "color": "#b3b3b3"},
            {"name": "Seen (No Flag)", "query": "flag:0 -is:new", "color": "#666666"}
        ]
        
        self.legend_ui_elements = []
        
        self.setup_ui()
        self.load_decks()

    def setup_ui(self):
        self.main_layout = QHBoxLayout(self)
        
        # --- LEFT PANEL (DECK LIST) ---
        left_panel = QVBoxLayout()
        
        deck_label = QLabel("Select Deck:")
        deck_label.setStyleSheet("font-weight: bold; font-size: 14px;")
        left_panel.addWidget(deck_label)
        
        self.deck_list = QListWidget()
        self.deck_list.setMinimumWidth(250)
        
        # --- CUSTOM SCROLLBAR CSS ---
        # Deixa a barra de rolagem mais grossa e visível
        self.deck_list.setStyleSheet("""
            QListWidget {
                font-size: 13px; 
                padding: 5px;
            }
            QScrollBar:vertical {
                border: none;
                background: rgba(0, 0, 0, 0.1);
                width: 16px; /* LARGURA DA BARRA AQUI */
                margin: 0px;
            }
            QScrollBar::handle:vertical {
                background: #666666;
                min-height: 30px;
                border-radius: 8px;
            }
            QScrollBar::handle:vertical:hover {
                background: #888888;
            }
            QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical {
                height: 0px;
            }
            QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical {
                background: none;
            }
        """)
        
        self.deck_list.currentItemChanged.connect(self.update_stats)
        
        left_panel.addWidget(self.deck_list)
        self.main_layout.addLayout(left_panel, 1)

        # --- RIGHT PANEL (CHART & LEGEND) ---
        right_panel = QVBoxLayout()
        
        # Chart
        self.chart = PieChartWidget()
        right_panel.addWidget(self.chart)

        # Legend
        self.legend_layout = QVBoxLayout()
        self.legend_layout.setSpacing(4)
        
        for cat in self.categories:
            row = QHBoxLayout()
            
            color_box = QLabel()
            color_box.setFixedSize(16, 16)
            color_box.setStyleSheet(f"background-color: {cat['color']}; border-radius: 4px; border: 1px solid #333;")
            
            text_label = QLabel()
            text_label.setStyleSheet("font-size: 14px;")
            
            row.addWidget(color_box)
            row.addWidget(text_label)
            row.addStretch()
            
            self.legend_layout.addLayout(row)
            
            self.legend_ui_elements.append({
                "name": cat["name"],
                "color": cat["color"],
                "text_label": text_label
            })

        right_panel.addLayout(self.legend_layout)
        
        # Total
        self.total_label = QLabel("")
        self.total_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
        self.total_label.setStyleSheet("font-size: 15px; margin-top: 10px;")
        right_panel.addWidget(self.total_label)
        
        self.main_layout.addLayout(right_panel, 2)

    def load_decks(self):
        """Loads all Anki decks into the ListWidget"""
        self.deck_list.blockSignals(True)
        
        self.deck_list.addItem("All Decks")
        
        decks = sorted([deck.name for deck in mw.col.decks.all_names_and_ids()])
        for deck_name in decks:
            self.deck_list.addItem(deck_name)
            
        self.deck_list.blockSignals(False)
        
        self.deck_list.setCurrentRow(0)

    def update_stats(self, current_item=None, previous_item=None):
        """Calculates stats based on selection and updates UI"""
        if not self.deck_list.currentItem():
            return
            
        selected_deck = self.deck_list.currentItem().text()
        
        if selected_deck == "All Decks":
            deck_query = ""
        else:
            deck_query = f'deck:"{selected_deck}" ' 

        chart_data = []
        total_cards = 0
        
        for cat in self.categories:
            full_query = deck_query + cat["query"]
            count = len(mw.col.find_cards(full_query))
            chart_data.append((cat["name"], count, cat["color"]))
            total_cards += count

        # 1. Update Chart
        self.chart.set_data(chart_data)

        # 2. Update Legend Texts
        for i, data in enumerate(chart_data):
            name, value, color = data
            ui = self.legend_ui_elements[i]
            
            percentage = (value / total_cards * 100) if total_cards > 0 else 0
            
            font_weight = "bold" if value > 0 else "normal"
            text_color = "#ffffff" if value > 0 else "#888888"
            
            ui["text_label"].setText(f"<span style='font-weight:{font_weight}; color:{text_color};'>{name}:</span> {value} cards ({percentage:.1f}%)")

        # 3. Update Total
        self.total_label.setText(f"<b>Total analyzed:</b> {total_cards} cards")

def show_stats_dialog():
    if not mw.col:
        showInfo("Please open a user profile first.")
        return

    if not hasattr(mw, "japanese_flags_dialog") or not mw.japanese_flags_dialog.isVisible():
        mw.japanese_flags_dialog = FlagStatsDialog(mw)
        mw.japanese_flags_dialog.show()
    else:
        mw.japanese_flags_dialog.activateWindow()
        mw.japanese_flags_dialog.raise_()

# Create menu action
action = QAction("Japanese Stats (Flags)", mw)
action.triggered.connect(show_stats_dialog)

# Add to Tools menu
mw.form.menuTools.addAction(action)

I did exactly that but I don’t see Japanese under tools or add ons

It kinda does, but I have to type the colors and categories I want every time manually. Putting it into config also doesn’t work, I tried everything

Check if the file inside the addon folder looks exactly like this…
__init__.py
2 underscores before and 2 underscores after init.

The addon must be placed inside the addons21 folder; create a folder, for example, named teste.

Place the __init__.py file inside this folder.
Inside it, put the code I gave you. I’ve already tested it and it’s working.

Tools > Japanese

Oh, I am so sorry, I forgot the underscores. Thank you for your patience.

Now I get this:

Anki 25.09.2 (3890e12c) (ao)
Python 3.13.5 Qt 6.9.1 PyQt 6.9.1
Platform: Windows-11-10.0.22621-SP0

When loading Japanese:
Traceback (most recent call last):
File “C:\Users\owner\AppData\Local\AnkiProgramFiles.venv\Lib\site-packages\aqt\addons.py”, line 250, in loadAddons
import(addon.dir_name)
~~~~~~~~~~^^^^^^^^^^^^^^^^
File “C:\Users\owner\AppData\Roaming\Anki2\addons21\Japanese_init_.py”, line 49
self.setWindowTitle(“Japanese Stats (Flags)”)
^
SyntaxError: invalid character ‘“’ (U+201C)

Copy and save the new code that I edited.

The filename must be __init__.py and not japanese__init__.py

1 Like

Now it works. Thank you so so much.

2 Likes