You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在KivyMD中创建带透明效果的美观图表?

问题描述

我想要在KivyMD程序中创建一款美观的图表,样式参考svg.charts生成的带透明效果的图表。尝试用Kivy的Svg控件直接显示该SVG图表但失败;使用pylunasvg将其转为PNG时背景变为黑色。对比过kivy garden charts(样式生硬,不知如何设置透明和图例卡片)、akivymd chart(色彩繁杂易让用户疲劳),均不满意。请问如何在Kivy中创建类似目标样式的图表?

附原SVG转PNG代码:

from PIL import Image as pImage
from pylunasvg import Document
from numpy import array
from typing import Optional, Union
from os.path import join  # 补全原代码缺失的导入

def svg_to_image(svg_content: str,
                 savedir: Optional[str] = None,
                 filename: Optional[str] = None) -> Union[str, type(pImage)]:
    document = Document.loadFromData(svg_content)
    bitmap = document.renderToBitmap()

    img = pImage.fromarray(array(bitmap, copy=False))

    if savedir is not None:
        if filename is not None:
            if filename.endswith('.png'):
                _dir = join(savedir, filename)
            else:
                _dir = join(savedir, filename + '.png')
        elif savedir.endswith('.png'):
            _dir = savedir
        else:
            raise ValueError('Argument filename is None')
        img.save(_dir, format='png')
        return _dir
    else:
        return img
解决方案

方案一:修复pylunasvg转PNG的透明背景问题

pylunasvg默认会将SVG透明背景填充为黑色,只需在渲染时启用透明参数即可:

from PIL import Image as pImage
from pylunasvg import Document
from numpy import array
from typing import Optional, Union
from os.path import join

def svg_to_image(svg_content: str,
                 savedir: Optional[str] = None,
                 filename: Optional[str] = None) -> Union[str, type(pImage)]:
    document = Document.loadFromData(svg_content)
    # 启用透明背景渲染
    bitmap = document.renderToBitmap(transparent=True)
    # 转换为带alpha通道的RGBA图像
    img = pImage.fromarray(array(bitmap, copy=False)).convert("RGBA")

    if savedir is not None:
        if filename is not None:
            _dir = join(savedir, filename) if filename.endswith('.png') else join(savedir, filename + '.png')
        elif savedir.endswith('.png'):
            _dir = savedir
        else:
            raise ValueError('Argument filename is None')
        # 保存时保留透明通道
        img.save(_dir, format='png', transparency=0)
        return _dir
    else:
        return img

修改后生成的PNG会保留SVG的透明背景,直接用KivyMD的MDImage控件加载即可在界面显示。

方案二:自定义KivyMD图表组件(完全可控样式)

如果不想依赖SVG转图,可直接用Kivy的Canvas API手动绘制图表,完全控制透明度、样式和图例:

示例:带透明效果的折线图+图例卡片

from kivy.lang import Builder
from kivy.graphics import Color, Line, Rectangle
from kivy.metrics import dp
from kivymd.app import MDApp
from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.uix.widget import MDWidget

KV = '''
<ChartWidget>:
    size_hint: 1, 0.8
    padding: dp(20)
    MDBoxLayout:
        id: legend_box
        orientation: "horizontal"
        size_hint_y: None
        height: dp(40)
        pos_hint: {"top": 1}
        padding: dp(10)
        spacing: dp(20)

<LegendItem>:
    size_hint: None, 1
    width: dp(120)
    MDBoxLayout:
        orientation: "horizontal"
        spacing: dp(8)
        MDWidget:
            id: color_block
            size_hint: None, 1
            width: dp(16)
        MDLabel:
            text: root.label_text
            size_hint_x: 1
            halign: "left"

MDBoxLayout:
    orientation: "vertical"
    padding: dp(20)
    spacing: dp(10)
    ChartWidget:
'''

class LegendItem(MDBoxLayout):
    def __init__(self, color, label_text, **kwargs):
        super().__init__(**kwargs)
        self.label_text = label_text
        # 设置颜色块背景
        with self.ids.color_block.canvas:
            Color(*color)
            self.rect = Rectangle(pos=self.ids.color_block.pos, size=self.ids.color_block.size)
        self.ids.color_block.bind(pos=self.update_rect, size=self.update_rect)

    def update_rect(self, instance, value):
        self.rect.pos = instance.pos
        self.rect.size = instance.size

class ChartWidget(MDBoxLayout):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.bind(size=self.draw_chart)
        # 模拟数据
        self.data_series = [
            {"color": (0.2, 0.6, 0.8, 0.7), "data": [10, 20, 15, 25, 30], "label": "销量"},
            {"color": (0.8, 0.3, 0.5, 0.7), "data": [5, 15, 10, 20, 25], "label": "库存"},
        ]
        # 添加图例
        for series in self.data_series:
            self.ids.legend_box.add_widget(LegendItem(series["color"], series["label"]))

    def draw_chart(self, instance, value):
        self.canvas.before.clear()
        self.canvas.after.clear()
        padding = dp(20)
        chart_width = self.width - 2 * padding
        chart_height = self.height - dp(60)  # 预留图例空间
        max_value = max(max(series["data"]) for series in self.data_series)

        with self.canvas.after:
            # 绘制半透明网格
            Color(0.9, 0.9, 0.9, 0.5)
            # 水平网格线
            for i in range(5):
                y = padding + (chart_height / 4) * i
                Line(points=[padding, y, padding + chart_width, y], width=1)
            # 垂直网格线
            for i in range(len(self.data_series[0]["data"]) + 1):
                x = padding + (chart_width / (len(self.data_series[0]["data"]))) * i
                Line(points=[x, padding, x, padding + chart_height], width=1)

            # 绘制半透明折线与数据点
            for series in self.data_series:
                Color(*series["color"])
                points = []
                for idx, val in enumerate(series["data"]):
                    x = padding + (chart_width / (len(series["data"]) - 1)) * idx
                    y = padding + (val / max_value) * chart_height
                    points.extend([x, y])
                Line(points=points, width=dp(2))
                # 绘制数据点
                for idx, val in enumerate(series["data"]):
                    x = padding + (chart_width / (len(series["data"]) - 1)) * idx
                    y = padding + (val / max_value) * chart_height
                    Rectangle(pos=(x - dp(3), y - dp(3)), size=(dp(6), dp(6)))

class ChartApp(MDApp):
    def build(self):
        return Builder.load_string(KV)

if __name__ == "__main__":
    ChartApp().run()

该示例特点:

  • 折线、网格均通过颜色的alpha通道设置透明度(第四个值范围0-1)
  • 图例卡片为自定义组件,样式简洁可控
  • 所有元素基于Kivy Canvas绘制,完美适配KivyMD风格

方案三:使用pycairo渲染SVG到Kivy纹理

如果Kivy原生Svg控件不支持你的SVG特性,可通过pycairo直接渲染SVG为Kivy纹理:

import cairo
from kivy.graphics.texture import Texture
from kivymd.uix.image import MDImage

def svg_to_kivy_texture(svg_content, width=800, height=600):
    # 创建SVG渲染表面
    svg_data = svg_content.encode('utf-8')
    surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height)
    ctx = cairo.Context(surface)
    # 加载并渲染SVG
    svg_surface = cairo.SVGSurface.create_from_string(svg_data, width, height)
    ctx.set_source_surface(svg_surface, 0, 0)
    ctx.paint()
    # 转换为Kivy纹理
    texture = Texture.create(size=(width, height))
    texture.blit_buffer(surface.get_data(), colorfmt='rgba', bufferfmt='ubyte')
    texture.flip_vertical()
    return texture

# 使用示例
# texture = svg_to_kivy_texture(your_svg_content)
# image = MDImage(texture=texture)

此方式能更好支持复杂SVG特性,同时保留透明背景。


内容的提问来源于stack exchange,提问作者DazzRick

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.17 02:25:38