当前位置: 首页 > news >正文

【D3.js in Action 3 精译_031】3.5.2 DIY实战:在 Observable 平台实现带数据标签的 D3 条形图并改造单元测试模块

当前内容所在位置(可进入专栏查看其他译好的章节内容)

  • 第一部分 D3.js 基础知识
    • 第一章 D3.js 简介(已完结)
      • 1.1 何为 D3.js?
      • 1.2 D3 生态系统——入门须知
      • 1.3 数据可视化最佳实践(上)
      • 1.3 数据可视化最佳实践(下)
      • 1.4 本章小结
    • 第二章 DOM 的操作方法(已完结)
      • 2.1 第一个 D3 可视化图表
      • 2.2 环境准备
      • 2.3 用 D3 选中页面元素
      • 2.4 向选择集添加元素
      • 2.5 用 D3 设置与修改元素属性
      • 2.6 用 D3 设置与修改元素样式
      • 2.7 本章小结
    • 第三章 数据的处理 ✔️
      • 3.1 理解数据(已完结)
      • 3.2 准备数据(已完结)
      • 3.3 将数据绑定到 DOM 元素(已完结)
        • 3.3.1 利用数据给 DOM 属性动态赋值
      • 3.4 让数据适应屏幕(已完结)
        • 3.4.1 比例尺简介(上篇)
        • 3.4.2 线性比例尺(中篇)
          • 3.4.2.1 基于 Mocha 测试 D3 线性比例尺(DIY 实战)
        • 3.4.3 分段比例尺(下篇)
          • 3.4.3.1 使用 Observable 在线绘制 D3 条形图(DIY 实战)
      • 3.5 加注图表标签(上篇)
        • 3.5.1 人物专访:Krisztina Szűcs(下篇)
        • 3.5.2 DIY实战:在 Observable 平台实战演练并进行单元测试 ✔️
      • 3.6 本章小结

文章目录

  • 3.5.2 DIY实战:在 Observable 实现带数据标签的 D3 条形图并改造单元测试模块
    • 1 起因
    • 2 经过
      • 2.1 完成条形图剩余部分——绘制数据标签
      • 2.2 用 AI 提示重构单元测试模块
      • 2.3 集成 Chai.js 的 expect 断言
      • 2.4 导出定制的 MyMocha 类及相关断言方法
    • 3 小结

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

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

前言
本篇不是书中的内容,只是昨晚看了自己翻译的那篇给匈牙利设计师 Krisztina Szűcs 做的人物专访,一时兴起,在 Observable 平台重新实现了一版第 3 章的条形图,顺便把上回遇到的单元测试问题一并解决了。建议大家也多动动手,到 Observable 从头开始敲一遍代码,巩固所学。

3.5.2 DIY实战:在 Observable 实现带数据标签的 D3 条形图并改造单元测试模块

1 起因

学完了第三章,我也在本地实测了一遍,效果还不错。于是就想着同步更新一下放到 Observable 上的版本。没曾想竟然在单元测试模块卡住了:Observable 居然不支持 Mocha.js 这样的测试框架,无法使用全局的 describeit 方法来写测试套件!除了支持 Chai.js 断言库的 CDN 引入,其余效果都得自己封装。网上倒是有几个现成的案例,但要么过于简单,只是粗略对断言模块 expect 方法的封装 1

图 1 对 Jest 的 expect 断言做简单封装的效果图

【图 1 对 Jest 的 expect 断言做简单封装的效果图】

要么又过于复杂 2

图 2 同样基于 Jest 的 expect 断言实现的一套定制测试框架

【图 2 同样基于 Jest 的 expect 断言实现的一套定制测试框架】

而我只希望能用上 describeit,最后将单元测试写到一个测试套件(suite)里,大致长这样:

图 3 希望通过组合 describe 和 it 方法实现的单元测试效果

【图 3 希望通过组合 describe 和 it 方法实现的单元测试效果】

没办法,Observable 这方面还不成熟,还得自力更生。

2 经过

2.1 完成条形图剩余部分——绘制数据标签

参考上一节做好的版本(详见我的《3.4 小节 DIY 实战:使用 Observable 在线绘制 D3 条形图》),先把带标签的 D3 条形图画出来。

和上次一样,先上传 data.csv 原始数据集,然后转成 Observable 可以使用的对象数组:

data = {const csv = await FileAttachment("data.csv").csv({typed: true});return csv.sort((a, b) => d3.descending(a.count, b.count));
}

接着定义两个方向上的比例尺,放到一个 JavaScript 对象里备用:

scales = {const x = d3.scaleLinear().domain([0, d3.max(data, (d) => d.count)]).range([0, 450]);const y = d3.scaleBand().domain(data.map((d) => d.technology)).range([0, 700]).paddingInner(0.2);return { x, y };
}

然后就可以绘制条形图了,定义一个图表变量 chart

chart = {const svg = d3.create("svg").attr("viewBox", "0 0 600 700").attr("width", "100%")// .style('border', '1px solid black');const groups = svg.selectAll("g").data(data).join("g").attr("transform", (d) => `translate(0, ${scales.y(d.technology)})`);// append rectsappendRect(groups);// append tech name labelsappendTechNameLabels(groups);// append count labelsappendCountLabels(groups);// data binding partappendAxisLine(svg);return svg.node();
}

由于要加注两组标签,要用到 SVG 的分组元素(g),这里需要现将数据绑定到每个 <g> 元素上(如第 8 行所示)。然后用 groups 选择集分别完成矩形条、名称标签以及数据标签的绑定与绘制。为了方便查看,我把它们都提到了单独的单元格来处理(践行“单一职责”原则)。

先是技术名称标签。我再原书内容的基础上,把 D3.js 对应的得票数也设置了一些样式(加粗、变色、调整字号):

function appendCountLabels(groups) {// Define predicatesconst target = 'D3.js';const fontSizeHightD3 = ({technology: t}) => (t === target) ? '9px' : '8px';const fontWeightByTechName = ({technology: t}) => (t === target) ? 700 : 400;const fillColorByTechName = ({technology: t}) => (t === target) ? 'yellowgreen' : '#000';// Append labelsgroups.append('text').attr('x', d => 100 + scales.x(d.count) + 4).attr('y', 12).text(d => d.count).style('font-family', 'sans-serif').style('font-weight', fontWeightByTechName).style('font-size', fontSizeHightD3).style('fill', fillColorByTechName);
}

效果还不赖:

图 4 升级版的 D3 数据标签效果

【图 4 升级版的 D3 数据标签效果】

接着绘制纵轴标签(对应各技术名称):

function appendTechNameLabels(groups) {groups.append('text').attr('x', 96).attr('y', 12).attr('text-anchor', 'end').text(d => d.technology).style('font-family', 'sans-serif').style('font-size', '10px');
}

然后是矩形条:

function appendRect(groups) {const byTechName = ({technology: t}) => t === 'D3.js' ? 'yellowgreen' : 'skyblue';groups.append('rect').attr('x', 100).attr('y', 0).attr('height', scales.y.bandwidth()).attr('width', d => scales.x(d.count)).attr('fill', byTechName);
}

最后是纵轴的那条直线:

function appendAxisLine(svg) {svg.append('line').attr('x1', 100).attr('y1', 0).attr('x2', 100).attr('y2', 700).attr('stroke', 'black');
}

然后 Shift + Enter 一键出图:

图 5 最终在 Observable 平台绘制的加注了图表标签的 D3 条形图效果

【图 5 最终在 Observable 平台绘制的加注了图表标签的 D3 条形图效果】

2.2 用 AI 提示重构单元测试模块

接下来才是本篇的重头戏——自己封装一套 describe 方法和 it 方法。还好 Observable 支持断言库 Chai.js 的导入,可能在 Mike Bostock 大神看来,只要把断言结果放到单元格里就行了,干嘛要写成 describe 嵌套 it 的结构呢?对于想用 JS 的循环结构来写测试的码畜的想法,大神可能无暇顾及:

// 这是我精心构建的测试数据(多么优雅~我居然还会用 Map)
testData = new Map([[198, 83],[414, 173],[852, 256], // backup: 852 -> 356[1078, 450]
]);

本来【图1】是出不来效果的,因为 it_old 方法最初的定义是这样的:

/*** Test helper to display test title into the notebook*/
function it_old(title, testFunction) {try {testFunction.call(this);return html`<div style="color:green;" >✓ : ${title || "Test passing "}</div>`;} catch (err) {return html`<div style="color:red;" >× : ${err.message}</div>`;}
}

如果不逐个返回运行的结果,就会乱套:

invalidResults = {it_old("test1", () => expect(2).to.be.lessThan(1));it_old("test2", () => expect(5).to.be.lessThan(1));it_old("test3", () => expect(10).to.be.lessThan(1));it_old("test4", () => expect(100).to.be.lessThan(1));
}

运行单元格后看不到任何报错:

图 6 无法将测试结果正确显示到页面旧版 it 方法

【图 6 无法将测试结果正确显示到页面旧版 it 方法】

这么一来,我要封装的 it 方法和 describe 方法,必须自动收集这样的断言结果才行,而且还得在后台完成,不然太 low,与我的码畜风格相悖。于是我想到了 ES6 引入的 class 语法糖,先把 describeit 定义的回调函数收集到类的一个成员数组,运行的时候再用 this 去挨个遍历它们,结果放到另一个数组,最后用统一的渲染函数交卷,不就搞定了吗?

想法成形,下一步就让机智的 AI 帮我出个 0.1 版吧。果然,不抱太大希望的情况下往往有惊喜,居然帮我把 beforeHooksafterHooks 都实现了(先不论对错,这么端正的态度就值得表扬):

class TestSuite {constructor(name) {this.name = name;this.tests = [];this.beforeHooks = [];this.afterHooks = [];}describe(name, fn) {const suite = new TestSuite(name);fn.call(suite);this.tests.push(suite);}it(name, fn) {this.tests.push({ name, fn });}before(fn) {this.beforeHooks.push(fn);}after(fn) {this.afterHooks.push(fn);}async run() {console.log(`Running suite: ${this.name}`);// Run before hooksfor (const hook of this.beforeHooks) {await hook();}for (const test of this.tests) {if (typeof test.fn === 'function') {try {await test.fn();console.log(`✔️ ${test.name}`);} catch (error) {console.error(`${test.name}`);console.error(error);}} else {// Recursively run nested suitesawait test.run();}}// Run after hooksfor (const hook of this.afterHooks) {await hook();}}
}// 使用示例
const suite = new TestSuite('My Test Suite');suite.describe('Array', function() {this.before(() => {console.log('Setting up before tests...');});this.after(() => {console.log('Cleaning up after tests...');});this.it('should add items', async () => {const arr = [];arr.push(1);if (arr.length !== 1) throw new Error('Test failed');});this.it('should remove items', async () => {const arr = [1];arr.pop();if (arr.length === 0) throw new Error('Test failed');});
});suite.run();

直接放到 Observable 单元格运行,虽然有很多小问题,但总算还像那么回事:

图 7 根据 AI 提示词生成的制定代码效果截图

【图 7 根据 AI 提示词生成的制定代码效果截图】

2.3 集成 Chai.js 的 expect 断言

AI 版本过于粗糙,需要调整几个地方:

  1. 控制台输出需要改为页面显示;
  2. 各单元测试结果需要分别收集起来;
  3. 测试套件和用例描述也得放到结果里;
  4. 统一整体输出样式(颜色、缩进等)。

逐一解决这些小瑕疵,于是就有了 v1.0 版的测试类 MyMocha

// Define customized Mocha class
class MyMocha {constructor(name) {this.name = name;this.tests = [];this.results = [md`<div style="font-weight: 700;">🚩 ${name}</div>`];this.beforeHooks = [];this.afterHooks = [];}describe(name, fn) {const suite = new MyMocha(name);fn.call(suite);this.tests.push(suite);this.results.push(md`<div style="font-weight: 700; text-indent: 1em;">⏳ <i>${name}</i></div>`);}it(name, fn) {this.tests.push({ name, fn });}before(fn) {this.beforeHooks.push(fn);}after(fn) {this.afterHooks.push(fn);}// show the results altogether in markdown formatasync showResults() {await this.run();return md`${this.results}`;}isFunction(fn) {return typeof fn === "function";}async run() {console.log(`Running suite: ${this.name}`);// Run before hooksfor (const hook of this.beforeHooks) {await hook();}for (const test of this.tests) {if (this.isFunction(test.fn)) {try {await test.fn();this.results.push(html`<div style="color: green; text-indent: 2em;">✔️ ${test.name}</div>`);} catch (error) {this.results.push(html`<div style="color:red; text-indent: 2em;">❌ ${test.name}</div>`);this.results.push(html`<div style="color: red; text-indent: 3em;">${error.message}</div>`);}} else {// Recursively run nested suitesawait test.run();}}// Run after hooksfor (const hook of this.afterHooks) {await hook();}}
}

然后把 Chai.js 导入,再把 expect 断言提出来:

chai = import("https://unpkg.com/chai/chai.js");
expect = chai.expect.bind(chai);

写个测试看看:

suite = {const testData = new Map([[198, 83],[414, 173],[852, 256], // backup: 852 -> 356[1078, 450]]);const suite = new MyMocha("DIY mocha test:");const it = suite.it.bind(suite);const describe = suite.describe.bind(suite);describe("Testing horizontal scale for my bar chart:", () => {testData.forEach((expected, domain) => {it(`Pass the value ${domain} to the xScale() function, should return ${expected}.`, () => {const actual = scales.x(domain);const diff = Math.abs(actual - expected);expect(diff, "[Diff Exceeded]").to.be.lessThan(1);});});});return suite.showResults();
}

效果还行:

图 8 集成了 Chai.js 的 expect 断言后的测试用例运行结果

【图 8 集成了 Chai.js 的 expect 断言后的测试用例运行结果】

2.4 导出定制的 MyMocha 类及相关断言方法

既然都测试通过了,就可以考虑放到一个新的 Notebook 里,供其它记事本导入了。咱也模仿一下其他网友的套路,搞个标题和用法示例:

图 9 拟用于导出 MyMocha 类和 expect 断言的通用 Notebook 页面

【图 9 拟用于导出 MyMocha 类和 expect 断言的通用 Notebook 页面】

然后将该页面设置为公开访问,并根据 Observable 的官方文档,用规定的导入语法再写一版测试:

图 10 将 Notebook 记事本页面设置为公开访问

【图 10 将 Notebook 记事本页面设置为公开访问】

图 11 从页面右侧边栏的官方文档找到导入其他记事本单元格的写法

【图 11 从页面右侧边栏的官方文档找到导入其他记事本单元格的写法】

按照官方文档,导入要这么写:

import { MyMocha, expect } from "@anton-playground/combined-unit-tests"

再测一遍,结果发现一个 Bug:渲染完成后没有及时清空本次测试结果,导致重复运行后上次的结果也在里面。于是切回公共页面改改渲染函数的逻辑,勉强算是 v1.1 版吧:

// show the results altogether in markdown format
async showResults() {await this.run();const results = md`${this.results}`;this.tests = this.tests.filter((t) => !this.isFunction(t.fn));this.results = [];return results;
}

再测,大功告成:

图 12 最终通过导入公共记事本的自定义方法实现的测试套件的实际效果

【图 12 最终通过导入公共记事本的自定义方法实现的测试套件的实际效果】

3 小结

虽然成功模拟了 Mocha.js 里的 describeit 原语,但毕竟逻辑过于简单,稍微上点有难度的测试就不够用了,而且写法上也没有 Mocha.js 那么自然,对于锚定的几个 hooks 钩子也无暇验证。这个 Notebook 就算抛砖引玉吧,以后对 TDDBDD 了解得更深入了再来升级。

两个记事本页面我都共享出来,方便大家学习交流(可以 Fork 到自己的工作空间(Workspace)进行修改):

  • 定制的 MyMocha 测试类:https://observablehq.com/@anton-playground/combined-unit-tests
  • 加注标签的条形图并通过线性比例尺单元测试的示例页:https://observablehq.com/@anton-playground/my-bar-chart-with-chaijs


  1. 搜到一篇对 Jest 的 expect 方法的轻量级封装案例,详见:Spencer: Unit testing inside a notebook ↩︎

  2. 详见:Tom Larkworthy: Reactive Unit Testing and Reporting Framework ↩︎

相关文章:

【D3.js in Action 3 精译_031】3.5.2 DIY实战:在 Observable 平台实现带数据标签的 D3 条形图并改造单元测试模块

当前内容所在位置&#xff08;可进入专栏查看其他译好的章节内容&#xff09; 第一部分 D3.js 基础知识 第一章 D3.js 简介&#xff08;已完结&#xff09; 1.1 何为 D3.js&#xff1f;1.2 D3 生态系统——入门须知1.3 数据可视化最佳实践&#xff08;上&#xff09;1.3 数据可…...

华为OD机试真题-字符串分割

题目描述&#xff1a; 给定非空字符串s&#xff0c;将该字符串分割成一些子串&#xff0c;使每个子串的ASCII码值的和均为水仙花数。 1、若分割不成功&#xff0c;则返回0。 2、若分割成功且分割结果不唯一&#xff0c;则返回-1。 3、若分割成功且分割结果唯一&#xff0c;则返…...

编程技巧:提高代码健壮性与可维护性的关键方法(以 Shell 为例)

在脚本编写和自动化工作中,良好的编程技巧对于确保代码的健壮性和可维护性至关重要。以下是一些关键的编程技巧,包括模块化设计、单元测试、版本控制、处理边界条件、错误处理、中间值保存和创建 Flag。本文将通过 Shell 脚本示例来阐述这些技巧的应用。 1. 模块化设计 **定…...

【无标题】ReadableStream is not defined

升级 node 版本到 18 及以上即可解决...

【JVM】高级篇

1 GraalVM 1.1 什么是GraalVM GraalVM是Oracle官方推出的一款高性能JDK&#xff0c;使用它享受比OpenJDK或者OracleJDK更好的性能。 GraalVM的官方网址&#xff1a;https://www.graalvm.org/ 官方标语&#xff1a;Build faster, smaller, leaner applications。 更低的CPU…...

nacos1.4源码-服务发现、心跳机制

nacos的服务发现主要采用服务端主动推送客户端定时拉取&#xff1b;心跳机制通过每5s向服务端发送心跳任务来保活&#xff0c;当超过15s服务端未接收到心跳任务时&#xff0c;将该实例设置为非健康状态&#xff1b;当超过30s时&#xff0c;删除该实例。 1.服务发现 nacos主要采…...

C++ 2D平台游戏开发案例

关于2D平台游戏的C开发案例&#xff0c;包括游戏设计、实现细节、图形渲染和音效处理等内容。虽然无法一次性提供3000字&#xff0c;但我会尽量详细描述各个部分&#xff0c;并确保有足够的深度和广度。 2D平台游戏开发案例 一、游戏设计 游戏概述 游戏名称&#xff1a;“冒险…...

【Webpack--019】TreeShaking

&#x1f913;&#x1f60d;Sam9029的CSDN博客主页:Sam9029的博客_CSDN博客-前端领域博主 &#x1f431;‍&#x1f409;若此文你认为写的不错&#xff0c;不要吝啬你的赞扬&#xff0c;求收藏&#xff0c;求评论&#xff0c;求一个大大的赞&#xff01;&#x1f44d;* &#x…...

Docker基本操作命令

Docker 是一个开源的应用容器引擎&#xff0c;允许开发者打包应用以及其依赖包到一个可移植的容器中&#xff0c;然后发布到任何流行的 Linux 机器上&#xff0c;也可以实现虚拟化。容器是完全使用沙箱机制&#xff0c;相互之间不会有任何接口。主要功能是为开发者提供一个简单…...

开源计算器应用的全面测试计划:确保功能性和可靠性

✅作者简介&#xff1a;2022年博客新星 第八。热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 &#x1f49e;当前专栏…...

uni.requestPayment 支付成功之后会走 wx.onAppRoute

uni.requestPayment 是用于发起微信支付的统一接口&#xff0c;而 wx.onAppRoute 是用于监听小程序的路由变化。当 uni.requestPayment 支付成功后&#xff0c;如果发生了页面跳转或者其他路由变化&#xff0c;wx.onAppRoute 会被触发。这个行为是正常的&#xff0c;因为支付成…...

统⼀服务入口 - Gateway

网关介绍 问题 在 spring cloud 体系中我们通过 Eureka,Nacos 解决了服务注册,服务发现的问题,使⽤Spring Cloud LoadBalance解决了负载均衡的问题,使⽤ OpenFeign 解决了远程调⽤的问题. 但是当前所有微服务的接⼝都是直接对外暴露的,可以直接通过外部访问.为了保证对外服务的…...

QGraphicsWidget Class

Header:#include < QGraphicsWidget > qmake:QT += widgets Since:Qt 4.4 Inherits:QGraphicsObject and QGraphicsLayoutItem Inherited By:QGraphicsProxyWidget This class was introduced in Qt 4.4. Public Types enum anonymous {Type }Properties autoFi…...

探讨最好用的AI工具:从日常到创新的应用

文章目录 引言常用AI工具1. 语音助手2. 图像识别软件3. 机器翻译工具4. 智能客服系统 创新AI应用1. 自动驾驶汽车2. 虚拟试衣间3. 医疗影像分析4. 个性化推荐系统 个人体验分享1. 通义灵码2. 文心一言3. 智能写作助手4. 智能家居设备5. DALLE6. Whisper7. Codex8. Gym9. ChatGP…...

Python系统教程005(字符串的格式化输出)

知识回顾 1、默认情况下&#xff0c;input函数接收的数据是字符串类型。 2、字符串类型的关键词是str。 3、\n和\t都是转义字符&#xff0c;\n用来换行&#xff0c;\t用来留出一段固定长度的空白。 4、type函数能够用来查看变量的数据类型 5、数据类型的转换&#xff0c;举…...

六款电脑远程控制软件分享,2024最热门软件合集,总有一款适合你!速来看!

想要随时随地控制自己的电脑&#xff1f; 无论你是办公需求&#xff0c;还是要远程协助他人&#xff0c;一款好用的远程控制软件绝对少不了。 2024年最热门的六款远程控制软件已经为你准备好&#xff0c;总有一款适合你&#xff0c;赶快往下看吧&#xff01; 1. 安企神系统—…...

优质微信群不再难寻!掌握这些技巧就够了!

在当今信息爆炸的时代&#xff0c;微信群已成为人们交流思想、分享知识、建立人脉的重要平台。无论是专业领域的深入探讨&#xff0c;还是兴趣爱好的自由交流&#xff0c;微信群都能为你提供一个即时互动的虚拟空间。然而&#xff0c;面对海量的微信群信息&#xff0c;如何高效…...

python - mysql操作

Python MySQL 操作 1. 背景介绍 常见的Mysql驱动介绍&#xff1a; MySQL-python&#xff1a;也就是MySQLdb。是对C语言操作MySQL数据库的一个简单封装。遵循了Python DB API v2。但是只支持Python2&#xff0c;目前还不支持Python3。mysqlclient&#xff1a;是MySQL-python的…...

基于Springboot+Vue的服装生产管理信息系统设计与实现(含源码数据库)

1.开发环境 开发系统:Windows10/11 架构模式:MVC/前后端分离 JDK版本: Java JDK1.8 开发工具:IDEA 数据库版本: mysql5.7或8.0 数据库可视化工具: navicat 服务器: SpringBoot自带 apache tomcat 主要技术: Java,Springboot,mybatis,mysql,vue 2.视频演示地址 3.功能 在这个…...

75.【C语言】文件操作(2)

承接74.【C语言】文件操作(1)文章 目录 5.详细阐释文件的打开和关闭 1.流 2.标准流 3.文件指针 FILE 两层含义 附:FILE的头文件 4.操作文件的步骤 1.fopen函数 ​编辑 简写的全称查询 输入&输出的含义 2.fclose函数 3.代码示例 补充:绝对路径和相对路径 注意…...

css实现圆环展示百分比,根据值动态展示所占比例

代码如下 <view class""><view class"circle-chart"><view v-if"!!num" class"pie-item" :style"{background: conic-gradient(var(--one-color) 0%,#E9E6F1 ${num}%),}"></view><view v-else …...

Admin.Net中的消息通信SignalR解释

定义集线器接口 IOnlineUserHub public interface IOnlineUserHub {/// 在线用户列表Task OnlineUserList(OnlineUserList context);/// 强制下线Task ForceOffline(object context);/// 发布站内消息Task PublicNotice(SysNotice context);/// 接收消息Task ReceiveMessage(…...

【SQL学习笔记1】增删改查+多表连接全解析(内附SQL免费在线练习工具)

可以使用Sqliteviz这个网站免费编写sql语句&#xff0c;它能够让用户直接在浏览器内练习SQL的语法&#xff0c;不需要安装任何软件。 链接如下&#xff1a; sqliteviz 注意&#xff1a; 在转写SQL语法时&#xff0c;关键字之间有一个特定的顺序&#xff0c;这个顺序会影响到…...

Python爬虫(二):爬虫完整流程

爬虫完整流程详解&#xff08;7大核心步骤实战技巧&#xff09; 一、爬虫完整工作流程 以下是爬虫开发的完整流程&#xff0c;我将结合具体技术点和实战经验展开说明&#xff1a; 1. 目标分析与前期准备 网站技术分析&#xff1a; 使用浏览器开发者工具&#xff08;F12&…...

CRMEB 框架中 PHP 上传扩展开发:涵盖本地上传及阿里云 OSS、腾讯云 COS、七牛云

目前已有本地上传、阿里云OSS上传、腾讯云COS上传、七牛云上传扩展 扩展入口文件 文件目录 crmeb\services\upload\Upload.php namespace crmeb\services\upload;use crmeb\basic\BaseManager; use think\facade\Config;/*** Class Upload* package crmeb\services\upload* …...

Maven 概述、安装、配置、仓库、私服详解

目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...

论文笔记——相干体技术在裂缝预测中的应用研究

目录 相关地震知识补充地震数据的认识地震几何属性 相干体算法定义基本原理第一代相干体技术&#xff1a;基于互相关的相干体技术&#xff08;Correlation&#xff09;第二代相干体技术&#xff1a;基于相似的相干体技术&#xff08;Semblance&#xff09;基于多道相似的相干体…...

深入浅出深度学习基础:从感知机到全连接神经网络的核心原理与应用

文章目录 前言一、感知机 (Perceptron)1.1 基础介绍1.1.1 感知机是什么&#xff1f;1.1.2 感知机的工作原理 1.2 感知机的简单应用&#xff1a;基本逻辑门1.2.1 逻辑与 (Logic AND)1.2.2 逻辑或 (Logic OR)1.2.3 逻辑与非 (Logic NAND) 1.3 感知机的实现1.3.1 简单实现 (基于阈…...

如何更改默认 Crontab 编辑器 ?

在 Linux 领域中&#xff0c;crontab 是您可能经常遇到的一个术语。这个实用程序在类 unix 操作系统上可用&#xff0c;用于调度在预定义时间和间隔自动执行的任务。这对管理员和高级用户非常有益&#xff0c;允许他们自动执行各种系统任务。 编辑 Crontab 文件通常使用文本编…...

Qt 事件处理中 return 的深入解析

Qt 事件处理中 return 的深入解析 在 Qt 事件处理中&#xff0c;return 语句的使用是另一个关键概念&#xff0c;它与 event->accept()/event->ignore() 密切相关但作用不同。让我们详细分析一下它们之间的关系和工作原理。 核心区别&#xff1a;不同层级的事件处理 方…...