【D3.js in Action 3 精译_046】DIY 实战:在 Observable 平台利用饼图布局函数实现 D3 多个环形图的绘制

当前内容所在位置:

  • 第五章 饼图布局与堆叠布局 ✔️
    • 5.1 饼图和环形图的创建 ✔️
      • 5.1.1 准备阶段(一)
      • 5.1.2 饼图布局生成器(二)
      • 5.1.3 圆弧的绘制(三)
      • 5.1.4 数据标签的添加(四)
      • 5.1.5 DIY-在 Observable 平台实现多个 D3 环形图的绘制 ✔️

文章目录

  • DIY 实战:在 Observable 平台利用饼图布局函数实现 D3 多个环形图的绘制
    • 1. 需求描述
    • 2. 具体步骤
      • 2.1 上传并初始化数据集
      • 2.2 按边距约定声明所需尺寸和常量
      • 2.3 迁移环形图中的比例尺
      • 2.4 绘制环形图模块的迁移
        • 2.4.1 明确总体思路
        • 2.4.2 添加 SVG 容器及内部绘图区
        • 2.4.3 指定年份的遍历逻辑
        • 2.4.4 基于年份生成带注解的环形图数据集
        • 2.4.5 单个环形图容器的绘制
        • 2.4.6 初始化圆弧生成工具
        • 2.4.7 绘制各环形片段的 path 元素
        • 2.4.8 每个环形图数据标签的绘制
        • 2.4.9 为每个环形图添加年份标签
      • 2.5 绘制 HTML 模板并渲染环形图

《D3.js in Action》全新第三版封面

《D3.js in Action》全新第三版封面

DIY 实战:在 Observable 平台利用饼图布局函数实现 D3 多个环形图的绘制


【写在前面】

最近一直在忙着更新我的 CSS 专栏《CSS in Depth 2》,趁着今天冬至顺利进军全书最后一个模块的学习,回头再来复盘一下《D3.js in Action》第五章 5.1 小节的知识点。时隔半月有余,这一节中关于多个拼图的绘制方法已经有所生疏了,真是应了那句 “拳不离手曲不离口” 的老话。没关系,之前有基础,多练练就找回感觉了。

1. 需求描述

将《D3.js in Action》第 3 版 5.1 节中演示的三个环形图,原封不动地迁移到 Observable 平台,如图 1 所示:

图 1 将在 Observable 平台同步实现的 5.1 节多个环形图效果

【图 1 将在 Observable 平台同步实现的 5.1 节多个环形图效果】

2. 具体步骤

2.1 上传并初始化数据集

首先登录 Observable 平台(https://observablehq.com/),并新建一个记事本页面。然后将 5.1 节附带的原始数据集文件(data.csv)上传至平台(随书源码详见我上传到 CSDN 下载频道的 zip压缩包):

图 2:将原始数据集上传至 Observable 平台

【图 2:将原始数据集上传至 Observable 平台】

接着,利用 FileAttachment 语法完成数据集的赋值:

data = await FileAttachment("data.csv").csv({ typed: true })

2.2 按边距约定声明所需尺寸和常量

由于本节的页面复杂度陡然提升,演示案例又没有借助任何前端框架,导致所有的全局变量(比如边距约定需要的 margin 对象等)都只能通过 <script> 标签引入,给项目练习和往 Observable 平台迁移带来的些许不便。因为用不了 import / export 语法,只能来回切换不同的 js 文件:

图 3:主页面 index.html 引入的各个 JavaScript 文件

【图 3:主页面 index.html 引入的各个 JavaScript 文件】

迁移时,我将作者提供的共享常量模块 shared-constants.js 一并放入 D3 边距约定中,并赋给常量 sizes

sizes = {
  const margin = { top: 50, right: 0, bottom: 50, left: 70 };
  const width = 1200;
  const height = 350;
  const innerWidth = width - margin.left - margin.right;
  const innerHeight = height - margin.top - margin.bottom;

  const formatsInfo = [
    { id: "vinyl", label: "Vinyl", color: "#76B6C2" },
    { id: "eight_track", label: "8-Track", color: "#4CDDF7" },
    { id: "cassette", label: "Cassette", color: "#20B9BC" },
    { id: "cd", label: "CD", color: "#2F8999" },
    { id: "download", label: "Download", color: "#E39F94" },
    { id: "streaming", label: "Streaming", color: "#ED7864" },
    { id: "other", label: "Other", color: "#ABABAB" }
  ];

  return {
    formatsInfo,
    margin,
    width,
    height,
    innerWidth,
    innerHeight
  };
}

这样就完成了两个全局变量的声明:

图 4:完成迁移的原始数据集 data 以及边距约定尺寸对象 sizes

【图 4:完成迁移的原始数据集 data 以及边距约定尺寸对象 sizes】

2.3 迁移环形图中的比例尺

根据作者的思路,环形图比例尺的初始化是在 scales.js 中实现的。有了之前的 data 常量,就可以将环形图需要的两个比例尺——分别用于设置横轴的 D3 分段比例尺,以及定义色阶用到的 D3 序数比例尺 —— 统一放到 scales 常量中备用:

scales = {
  const xScale = d3.scaleBand()
    .domain(data.map((d) => d.year))
    .range([0, innerWidth]);

  const colorScale = d3
    .scaleOrdinal()
    .domain(sizes.formatsInfo.map((d) => d.id))
    .range(sizes.formatsInfo.map((d) => d.color));

  return { xScale, colorScale };
}

这样,就只剩下环形图绘制模块 donut-charts.js 了。这也是迁移过程中最麻烦的一步。

2.4 绘制环形图模块的迁移

由于 Observable 平台无需定义专门的函数来控制 D3 图形渲染的开关,定义好一个单元格,导出的模块即在全局可用。因此,本地示例页中的启动模块(load-data.js)可以直接忽略。不过也可以用于模仿学习其中的命名规范:

// Load data
d3.csv("../data/data.csv", d3.autoType).then(data => {
  defineScales(data);  // scales.js
  drawDonutCharts(data); // donut-charts.js
  drawStackedBars(data); // 绘制堆积柱状图,与环形图无关,略
  drawStreamGraph(data); // 绘制流图,与环形图无关,略
  addLegend(); // 绘制图例,与环形图无关,略
});

而按照最终的环形图绘制模块,需要将 drawDonutCharts(data) 方法完整平移到 Observable 平台。这里先给出迁移前的本地版实现:

const drawDonutCharts = (data) => {

  /*******************************/
  /*    Append the containers    */
  /*******************************/
  // Append the SVG container
  const svg = d3.select("#donut")
    .append("svg")
      .attr("viewBox", `0 0 ${width} ${height}`);

  // Append the group that will contain the inner chart
  const donutContainers = svg
    .append("g")
      .attr("transform", `translate(${margin.left}, ${margin.top})`);
  
  /**********************************/
  /*    Create a donut chart for    */
  /*    each year of interest       */
  /**********************************/
  const years = [1975, 1995, 2013];
  const formats = data.columns.filter(format => format !== "year");
  years.forEach(year => {

    // Append a group for each year
    // and translate it to the proper position
    const donutContainer = donutContainers
      .append("g")
        .attr("transform", `translate(${xScale(year)}, ${innerHeight/2})`);

    // Prepare the data for the pie generator
    const yearData = data.find(d => d.year === year);
    const formattedData = [];
    formats.forEach(format => {
      formattedData.push({ format: format, sales: yearData[format] });
    });
    console.log("formattedData", formattedData);

    // Initialize the pie layout generator
    const pieGenerator = d3.pie()
      .value(d => d.sales);

    // Call the pie generator to obtain the annotated data
    const annotatedData = pieGenerator(formattedData);
    console.log("annotatedData", annotatedData)

    // Initialize the arc generator
    const arcGenerator = d3.arc()
      .startAngle(d => d.startAngle)
      .endAngle(d => d.endAngle)
      .innerRadius(60)
      .outerRadius(100)
      .padAngle(0.02)
      .cornerRadius(3);

    // Append the arcs
    const arcs = donutContainer
      .selectAll(`.arc-${year}`)
      .data(annotatedData)
      // .join("path")
      //   .attr("class", `arc-${year}`)
      //   .attr("d", arcGenerator)
      //   .attr("fill", d => colorScale(d.data.format));
        .join("g")
        .attr("class", `arc-${year}`);
    arcs
      .append("path")
        .attr("d", arcGenerator)
        .attr("fill", d => colorScale(d.data.format));
    arcs
      .append("text")
        .text(d => {
          d["percentage"] = (d.endAngle - d.startAngle) / (2 * Math.PI);
          return d3.format(".0%")(d.percentage);
        })
        .attr("x", d => {
          d["centroid"] = arcGenerator
            .startAngle(d.startAngle)
            .endAngle(d.endAngle)
            .centroid();
          return d.centroid[0];
        })
        .attr("y", d => d.centroid[1])
        .attr("text-anchor", "middle")
        .attr("dominant-baseline", "middle")
        .attr("fill", "#f6fafc")
        .attr("fill-opacity", d => d.percentage < 0.05 ? 0 : 1)
        .style("font-size", "16px")
        .style("font-weight", 500);

    // Append year labels
    donutContainer
      .append("text")
        .text(year)
        .attr("text-anchor", "middle")
        .attr("dominant-baseline", "middle")
        .style("font-size", "24px")
        .style("font-weight", 500);
  });
};

可以看到,绘制逻辑还是相当繁琐的。接下来一步步拆分。

2.4.1 明确总体思路

环形图的绘制主要分为以下步骤:

  1. 添加 SVG 容器及内部绘图区(即 SVG 分组元素 g);
  2. 遍历要绘制的三个年份,在循环体中依次绘制每个环形图 ——
    1. 添加单个环形图容器(g 元素);
    2. 数据源及辅助工具准备:
      1. 使用饼图布局函数,获取带标注信息的数据集(annotatedData);
      2. 初始化圆弧生成器(arcGenerator);
    3. 将单个环形图的各个片段归为一组,以便后续分别设置 path 元素与数据标签元素 text——
      1. 添加各环形片段的分组元素(记为 arcs);
      2. 基于 arcs 绘制各段圆弧(添加 path 并确定 d 属性值);
      3. 基于 arcs 绘制各段圆弧的数据标签(添加 text 并确定各标签的横纵坐标)。
    4. 导出 SVG 节点,方便页面其他单元格渲染完整图形。
2.4.2 添加 SVG 容器及内部绘图区

Observable 平台无需 DOM 操作,利用 d3.create('svg') 初始化 SVG 容器后导出即可:

svg = {
  const svg = d3
    .create("svg")
    .attr("viewBox", `0 0 ${sizes.width} ${sizes.height}`);

  // inner container
  const innerContainer = svg
    .append("g")
    .attr("transform", `translate(${sizes.margin.left}, ${sizes.margin.top})`);

  return svg;
}
2.4.3 指定年份的遍历逻辑

剩余步骤可按照以下结构先声明再逐步实现:

svg = {
  // svg & inner container part:
  // const svg = ...
  // const innerContainer = ...

  // annotated data
  const years = [1975, 1995, 2013];
  years.forEach((year) => {
    const annotatedData = getAnnotedData(year);

    // append donut container for each year's donut chart
    const donutContainer = appendDonutContainer(innerContainer, year);

    // arc generator
    const arcGenerator = getArcGenerator();

    // donut chart
    const arcs = donutContainer
      .selectAll(`.donut-${year}`)
      .data(annotatedData)
      .join("g")
      .attr("class", `donut-${year}`);

    // slice path
    const slicePaths = appendSlicePaths(arcs, arcGenerator);

    // labels for each slice
    const sliceLabels = appendSliceLabels(arcs, arcGenerator);

    // year label
    const yearLabel = appendYearLabel(year, donutContainer);
  });

  return svg;
}
2.4.4 基于年份生成带注解的环形图数据集

利用循环遍历的年份 year,筛选出当年的原始数据,然后与饼图布局函数结合,得到带注解的饼图数据集:

function getAnnotedData(year, rawData = data, fmts = formats) {
  const yearData = rawData.find((d) => d.year === year);
  const formattedData = fmts.map((format) => ({
    format,
    sales: yearData[format]
  }));

  // pie generator
  const pieGenerator = d3.pie().value((d) => d.sales);
  const annotatedData = pieGenerator(formattedData);
  return annotatedData;
}

这里的 pieGenerator 就是 D3 的饼图布局函数。

2.4.5 单个环形图容器的绘制

按照 2.4.1 确定的总思路,接下来实现每个环形图的容器逻辑:

function appendDonutContainer(innerContainer, year) {
  const offsetX = scales.xScale(year);
  const offsetY = sizes.innerHeight / 2;
  const donutContainer = innerContainer
    .append("g")
    .attr("transform", `translate(${offsetX}, ${offsetY})`);
  return donutContainer;
}
2.4.6 初始化圆弧生成工具

即实现 svg 模块中的这一句:

const arcGenerator = getArcGenerator();

其中的 getArcGenerator 由以下单元格定义:

function getArcGenerator() {
  return d3
    .arc()
    .startAngle((d) => d.startAngle)
    .endAngle((d) => d.endAngle)
    .innerRadius(60)
    .outerRadius(100)
    .padAngle(0.02)
    .cornerRadius(3);
}

注意:这里回调函数中的 d 是带注解的数据集,不是最初的原始数据集。

2.4.7 绘制各环形片段的 path 元素

这一步即实现:

// slice path
const slicePaths = appendSlicePaths(arcs, arcGenerator);

函数 appendSlicePaths 由以下单元格实现:

function appendSlicePaths(arcs, arcGenerator) {
  const { colorScale } = scales;
  const slicePaths = arcs
    .append("path")
    .attr("d", arcGenerator)
    .attr("fill", (d) => colorScale(d.data.format));
  return slicePaths;
}
2.4.8 每个环形图数据标签的绘制

即实现:

// labels for each slice
const sliceLabels = appendSliceLabels(arcs, arcGenerator);

其中 appendSliceLabels 定义如下:

function appendSliceLabels(arcs, arcGenerator) {
  const textFormatter = (d) => {
    d.percentage = (d.endAngle - d.startAngle) / (2 * Math.PI);
    const formatter = d3.format(".0%");
    return formatter(d.percentage);
  };

  const showLabelAbove5Pct = (d) => (d.percentage < 0.05 ? 0 : 1);

  const getCentroidX = initCentroidXAccessor(arcGenerator);
  const getCentroidY = (d) => d.centroid[1];

  const sliceLabels = arcs
    .append("text")
    .text(textFormatter)
    .attr("x", getCentroidX)
    .attr("y", getCentroidY)
    .attr("text-anchor", "middle")
    .attr("dominant-baseline", "middle")
    .attr("fill", "#f6fafc")
    .attr("fill-opacity", showLabelAbove5Pct)
    .style("font-size", "1em")
    .style("font-weight", 500);

  return sliceLabels;
}

这一步是整个迁移过程中的难点。arcs 其实是一个 g 容器元素,但绑定的数据集是当前年份各环形片段的子数据集,因此第 14 行中的 .append("text") 其实是一次性添加了多个数据标签,数量与环形片段的个数相等。

另外,数据标签的 xy 坐标,均是基于当前片段的形心(这里可以理解为重心)定位的。其中第 10 行使用了一个高阶函数来初始化形心 x 坐标的访问器函数:

function initCentroidXAccessor(arcGenerator) {
  return (d) => {
    d.centroid = arcGenerator
      .startAngle(d.startAngle)
      .endAngle(d.endAngle)
      .centroid();
    return d.centroid[0];
  };
}

可见,每个片段都利用了圆弧生成工具 arcGenerator 以及当前绑定的数据项 d 来初始化。

2.4.9 为每个环形图添加年份标签

最后是年份标签的添加,即实现 svg 中的这一行逻辑:

// year label
const yearLabel = appendYearLabel(year, donutContainer);

其中 appendYearLabel 定义如下:

function appendYearLabel(year, container) {
  const label = container
    .append("text")
    .text(year)
    .attr("text-anchor", "middle")
    .attr("dominant-baseline", "middle")
    .style("font-size", "1.5em")
    .style("font-weight", 500);
  return label;
}

这里为了让年份标签具有响应式特征,字号使用了 1.5em 而非参考代码中的 24px

2.5 绘制 HTML 模板并渲染环形图

最后创建一个单元格,用于在 Observable 平台渲染 HTML 模板。Observable 内置了 HTML 模板工具 htl,具体实现如下(HTML 节选自 index.html):

htl.html`
<div class="container">
		<h1 class="chart">Visualizing 40 Years of Music Industry Sales</h1>
		<div class="intro">This project visualizes the music sales in the United States by format between 1973 and 2019. These formats range from physical supports like <span class="support support-vinyl">vinyl</span>, <span class="support support-eigth-track">8-track</span>, <span class="support support-cassette">cassette</span>, and <span class="support support-cd">cd</span>, to music consumed digitally, with <span class="support support-download">downloads</span> and <span class="support support-streaming">streaming services</span>. The category <span class="support support-other">other</span> includes less prominent sources of revenue like music videos (physical), synchronization, and royalties.</div>
		<div id="donut">${svg}</div>
		<div class="source">
			<p>Data Source: <a class="demo" href="https://data.world/makeovermonday/2020w21-visualizing-40-years-of-music-industry-sales">MakeoverMonday 2020/W21</a></p>
			<p>Inspiration for the dataviz layout: <a class="demo" href="https://www.makeovermonday.co.uk/week-21-2020/">Submission of Laura Elliot</a></p>
		</div>
	</div>`

注意第 5 行,这里引入了 2.4 步中导出的 svg 节点常量。

关于 CSS 的设置,也可以用类似方法插入一个 <style> 标签(具体内容直接从 base.css 中复制粘贴即可):

htl.html`<style>
/* CSS styles */
</style>`

需要注意的是,通过这种方式设置的 CSS 样式会与 Observable 平台内置的 CSS 样式发生冲突,因此需要根据平台的渲染情况进行微调,比如将基础样式中的链接元素 aa:hovera:focus 调整为 a.demoa.demo:hovera.demo:focus,并同步更新 HTML 模板,添加对应的类名 demo

这样,就大功告成了!

图 5:D3 环形图的最终效果

【图 5:D3 环形图的最终效果】

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/940509.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【案例80】麒麟操作系统无法使用Uclient访问NC65

问题现象 麒麟操作系统&#xff0c;安装Uclient&#xff0c;添加应用后无法看到登录界面&#xff0c;一直在转圈。 问题分析 进入到Uclient的工作目录 发现在工作目录下&#xff0c;无相关app.log生成。 查看Uclient的main.log发现&#xff0c;有大量的报错与Uclient下的sha…

阿里云OSS批量导出下载地址 OSS批量导出 OSS导出清单

阿里云官方提供的客户端不能批量导出下载地址&#xff0c;阿里云OSS批量导出下载地址 OSS批量导出 OSS导出清单 1、参数配置&#xff1a;填写阿里云的AccessKeyID和AccessKeySecret&#xff0c;在阿里右上角的用户获取 2、选择地域&#xff1a;就是你OSS开的是哪个地方的&…

【CSS in Depth 2 精译_084】第 14 章:CSS 蒙版、形状与剪切概述 + 14.1:CSS 滤镜

当前内容所在位置&#xff08;可进入专栏查看其他译好的章节内容&#xff09; 第四部分 视觉增强技术 ✔️【第 14 章 蒙版、形状与剪切】 ✔️ 14.1 滤镜 ✔️ 14.1.1 滤镜的类型 ✔️14.1.2 背景滤镜 ✔️ 14.2 蒙版 文章目录 第 14 章 蒙版、形状与剪切 Masks, shapes, and…

如何高效调试复杂布局?Layout Inspector 的 Toggle Deep Inspect 完全解析

Layout Inspector 是 Android Studio 提供的一个强大工具&#xff0c;用于分析和调试 Android 应用的 UI 布局。前置条件是app是debug调试状态&#xff0c;它允许你在应用运行时实时查看布局层次结构、UI 元素的属性&#xff0c;并帮助你诊断 UI 渲染问题。 Toggle Deep Inspe…

wxpython 基础代码

wxpython 基础代码 import wxclass MyFrame(wx.Frame):def __init__(self):super().__init__(parentNone, title计算器, size(450, 250))panel wx.Panel(self)# panel.SetBackgroundColour(wx.GREEN)self.icon1 wx.Icon(name"test.ico", typewx.BITMAP_TYPE_PNG)se…

2.5 io_uring

io_uring的相关函数接口介绍 io_uring 是 Linux 内核中一种高效的异步 I/O 接口&#xff0c;最早引入于 **Linux 内核 5.1** 版本。它是由 Jens Axboe 开发的&#xff0c;目的是提供更高效的异步 I/O 操作&#xff0c;尤其是相比 epoll 和 aio&#xff0c;io_uring 减少了系统…

服务器数据恢复—V7000存储中多块磁盘出现故障导致业务中断的数据恢复案例

服务器存储数据恢复环境&#xff1a; 一台V7000存储上共12块SAS机械硬盘&#xff08;其中1块是热备盘&#xff09;&#xff0c;组建了2组Mdisk&#xff0c;创建了一个pool。挂载在小型机上作为逻辑盘使用&#xff0c;小型机上安装的AIXSybase。 服务器存储故障&#xff1a; V7…

python 读取win7 win10本机ipv6 地址转发到电邮(备份)

python 版本&#xff1a; 3.8.10 用于外网查询SMB服务器ipv6 地址。服务器定时查询本机ipv6地址&#xff0c;如地址变动则用电邮发送新地址。 import smtplib from email.mime.text import MIMEText from email.mime.multipart import MIMEMultipart import ssl import socket…

多个JAVA环境变量安装配置

在做java代码审计时&#xff0c;为了要成功运行目标环境&#xff0c;时长要对于jdk版进行切换&#xff0c;且在装多个jdk时还时长会遇到安装配置后环境变量不生效的情况&#xff0c;下文介绍&#xff1b; 1、为什么安装了新的jdk&#xff0c;有的时候环境变量中的jdk版本确还是…

数字经济下的 AR 眼镜

目录 1. &#x1f4c2; AR 眼镜发展历史 1.1 AR 眼镜相关概念 1.2 市面主流 XR 眼镜 1.3 AR 眼镜大事记 1.4 国内外 XR 眼镜 1.5 国内 AR 眼镜四小龙 2. &#x1f531; 关键技术 2.1 AR 眼镜近眼显示原理 2.2 AR 眼镜关键技术 2.3 AR 眼镜技术难点 3. &#x1f4a…

maven-resources-production:ratel-fast: java.lang.IndexOutOfBoundsException

Maven生产环境中遇到java.lang.IndexOutOfBoundsException的问题&#xff0c;尝试了重启电脑、重启IDEA等常规方法无效&#xff0c;最终通过直接重建工程解决了问题。 Rebuild Project 再启动OK

TDesign:NavBar 导航栏

NavBar 导航栏 左图&#xff0c;右标 appBar: TDNavBar(padding: EdgeInsets.only(left: 0,right: 30.w), // 重写左右内边距centerTitle:false, // 不显示标题height: 45, // 高度titleWidget: TDImage( // 左图assetUrl: assets/img/logo.png,width: 147.w,height: 41.w,),ba…

【计算机网络】lab2 Ethernet(链路层Ethernet frame结构细节)

&#x1f308; 个人主页&#xff1a;十二月的猫-CSDN博客 &#x1f525; 系列专栏&#xff1a; &#x1f3c0;计算机网络_十二月的猫的博客-CSDN博客 &#x1f4aa;&#x1f3fb; 十二月的寒冬阻挡不了春天的脚步&#xff0c;十二点的黑夜遮蔽不住黎明的曙光 目录 1. 前言 2.…

ansible剧本快速上手

playbook剧本介绍 是什么&#xff1a;能户长期保存&#xff0c;且能实现批量配置、部署…的文件格式&#xff1a;yaml格式。用 空格 冒号 头号 句号语法检测&#xff1a;ansible-playbook --syntax-check install-zabbix.yaml或则 -C检测取消默认任务&#xff1a;gather_facts…

【LeetCode每日一题】——434.字符串中的单词数

文章目录 一【题目类别】二【题目难度】三【题目编号】四【题目描述】五【题目示例】六【解题思路】七【时空频度】八【代码实现】九【提交结果】 一【题目类别】 字符串 二【题目难度】 简单 三【题目编号】 434.字符串中的单词数 四【题目描述】 统计字符串中的单词个…

C++ OpenGL学习笔记(1、Hello World空窗口程序)

终于抽出时间系统学习OpenGL 教程&#xff0c;同时也一步一步记录怎样利用openGL进行加速计算。 目录 1、环境准备1.1、库的下载1.2、库的选择及安装 2、OpenGL第一个项目&#xff0c;Hello World!2.1、新建hello world控制台项目2.2、配置openGL环境2.2.1 包含目录配置2.2.2 …

MySQL复制问题和解决

目录 环境介绍 一&#xff0c;主库执行delete&#xff0c;从库没有该数据 模拟故障 修复故障 二&#xff0c;主库执行insert&#xff0c;从库已存在该数据 模拟故障 故障恢复 三&#xff0c;主库执行update&#xff0c;从库没有该数据 模拟故障 故障恢复 四&#xf…

AWTK 在树莓派 pico 上的移植笔记

1. 配置文件 (awtk_config.h) pico 和 stm32f103 的配置差不多&#xff0c;虽然 pico 的内存要大不少&#xff0c;但是也不足提供一个完整的 FrameBuffer&#xff0c;所以只能使用片段 LCD。 我们在 awtk-stm32f103 的配置 基础稍作修改即可。 /* 使用片段 LCD */#define FRA…

构建MacOS应用小白教程(打包 签名 公证 上架)

打包 在package.json中&#xff0c;dependencies会被打进 Electron 应用的包里&#xff0c;而devDependencies则不会&#xff0c;所以必要的依赖需要放到dependencies中。files中定义自己需要被打进 Electron 包里的文件。以下是一个完整的 mac electron-builder的配置文件。 …

flink sink doris

接上文&#xff1a;一文说清flink从编码到部署上线 网上关于flink sink drois的例子较多&#xff0c;大部分不太全面&#xff0c;故本文详细说明&#xff0c;且提供完整代码。 1.添加依赖 <!--doris cdc--><!-- 参考&#xff1a;"https://doris.apache.org/zh-C…