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

news2024/10/9 15:32:59

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

  • 第一部分 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 rects
  appendRect(groups);

  // append tech name labels
  appendTechNameLabels(groups);

  // append count labels
  appendCountLabels(groups);

  // data binding part
  appendAxisLine(svg);

  return svg.node();
}

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

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

function appendCountLabels(groups) {
  // Define predicates
  const 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 labels
  groups
    .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 hooks
        for (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 suites
                await test.run();
            }
        }

        // Run after hooks
        for (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 format
  async showResults() {
    await this.run();
    return md`${this.results}`;
  }

  isFunction(fn) {
    return typeof fn === "function";
  }

  async run() {
    console.log(`Running suite: ${this.name}`);

    // Run before hooks
    for (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 suites
        await test.run();
      }
    }

    // Run after hooks
    for (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 ↩︎

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

PyQt5界面美化教程:一键切换四种风格

PyQt5界面美化教程&#xff1a;一键切换四种风格 关于作者 作者&#xff1a;小白熊 作者简介&#xff1a;精通python、matlab、c#语言&#xff0c;擅长机器学习&#xff0c;深度学习&#xff0c;机器视觉&#xff0c;目标检测&#xff0c;图像分类&#xff0c;姿态识别&#x…

【可答疑】基于51单片机的PWM控制智能台灯设计(含仿真、代码、报告、演示视频等)

✨哈喽大家好&#xff0c;这里是每天一杯冰美式oh&#xff0c;985电子本硕&#xff0c;大厂嵌入式在职0.3年&#xff0c;业余时间做做单片机小项目&#xff0c;有需要也可以提供就业指导&#xff08;免费&#xff09;~ &#x1f431;‍&#x1f409;这是51单片机毕业设计100篇…

vue 同一个页面第二次跳转路由内容不更新

问题出现原因 在vue中相同路由之间跳转&#xff0c;默认在跳转路由时会采用缓存策略,并不会刷新当前路由组件。导致mounted&#xff08;初始化&#xff09;,beforeDestory&#xff08;销毁&#xff09;等生命周期钩子函数并不会触发&#xff0c;从而产生路由跳转了&#xff0c;…

一文读懂Spring AOP的工作原理和机制(面试经)

导览 前言AOP(Aspect-Oriented Programming)必学必看1. 核心概念2. 主要原理3. 实践应用3.1 添加maven依赖3.2 定义切面Aspect3.3 定义Methods (join point) 切入点 结语精彩回顾 前言 在上文中&#xff0c;博主介绍了Spring IoC的核心原理和用法&#xff0c;相信你可以通过文…

Aria2Cloudreve任意文件写入到RCE

什么是Aria2 Aria2 是一个轻量级的命令行下载工具&#xff0c;支持多种下载协议&#xff0c;如 HTTP、FTP、SFTP、BitTorrent 和 Metalink。它以其强大的多源下载能力而著称&#xff0c;可以同时从多个服务器或对等节点下载文件&#xff0c;加快下载速度。Aria2 占用资源少&am…

UE4 材质学习笔记05(凹凸偏移和视差映射/纹理压缩设置)

一.凹凸偏移和视差映射 1.偏移映射 这需要一个高度图并且它的分辨率很低&#xff0c;只有256*256&#xff0c;事实上&#xff0c;如果高度图的分辨率比较低并且有点模糊&#xff0c;效果反而会更好 然后将高度图输出到BumpOffset节点的height插槽中&#xff0c; 之后利用得到…

JVM 内存模型与垃圾回收过程详解

JVM 内存模型与垃圾回收过程详解 文章目录 JVM 内存模型与垃圾回收过程详解1. JVM内存分区1.1 具体分区1.2 JVM内存分区的必要性 2. 垃圾回收2.1 CMS垃圾回收器2.2 G1垃圾回收器2.3 JVM垃圾回收从新生代到老年代 1. JVM内存分区 1.1 具体分区 Java虚拟机&#xff08;JVM&#…

YOLOv10改进策略【注意力机制篇】| 蒙特卡罗注意力(MCAttn)模块,提高小目标的关注度

一、本文介绍 本文记录的是基于蒙特卡罗注意力&#xff08;MCAttn&#xff09;模块的YOLOv10目标检测改进方法研究。利用蒙特卡罗注意力&#xff08;MCAttn&#xff09;模块提高RepNCSPELAN4模块的跨尺度特征提取能力&#xff0c;使模型能够更好地传递和融合提取的多尺度特征&…

<Rust>iced库(0.13.1)学习之部件(三十二):使用markdown部件来编辑md文档

前言 本专栏是学习Rust的GUI库iced的合集,将介绍iced涉及的各个小部件分别介绍,最后会汇总为一个总的程序。 iced是RustGUI中比较强大的一个,目前处于发展中(即版本可能会改变),本专栏基于版本0.12.1. 注:新版本已更新为0.13 概述 这是本专栏的第三十二篇,主要介绍一…

Matlab实现海鸥优化算法优化回声状态网络模型 (SOA-ESN)(附源码)

目录 1.内容介绍 2部分代码 3.实验结果 4.内容获取 1内容介绍 海鸥优化算法&#xff08;Seagull Optimization Algorithm, SOA&#xff09;是一种受海鸥觅食和飞行行为启发的群体智能优化算法。SOA通过模拟海鸥在空中搜寻食物、聚集和分散的行为模式&#xff0c;来探索和开发…

免费ppt模板从哪找?一文看这里

国庆假期结束&#xff0c;回工作岗位的你是不是正忙着准备汇报材料&#xff1f;别担心&#xff0c;一份精美的PPT能为你的工作汇报增色不少。 而一个吸引人的PPT背景模板&#xff0c;更是能让你的演示脱颖而出。 为了帮助你们快速找到高质量免费ppt模板背景&#xff0c;以下特…

C++:string(题目篇)

文章目录 前言一、字符串相加二、仅仅反转字母三、字符串中的第一个唯一字符四、字符串最后一个单词的长度五、检查字符串是否是回文六、反转字符串 II七、反转字符串中的单词 III八、字符串相乘总结 前言 学习了string后&#xff0c;我们来学习对应的习题。 一、字符串相加 …

SWIFT Payment

SWIFT stands for Society for Worldwide Interbank Financial Telecommunication SWIFT——环球银行金融电信协会 SWIFT Payment Useful Link ISO 20022https://www.iso20022.org/https://www.swift.com/standards/iso-20022MT and MX Equivalence Tableshttps://www2.swift…

Python验证码识别——前处理

目前不少系统的验证码做得越来越复杂&#xff0c;人眼都难以识别&#xff0c;尤其是QQ之类的验证码&#xff0c;想要识别&#xff0c;太难了。 现在有这样一个验证码&#xff1a; 一般的验证码识别&#xff0c;都是先进行前处理&#xff0c;然后分割&#xff0c;在进行识别。这…

Keysight 是德 EXR608A 实时示波器

Keysight 是德EXR608A 实时示波器 EXR608A、EXR604A EXR408A、EXR404A EXR258A、EXR254A EXR208A、EXR204A EXR108A、EXR104A EXR058A、EXR054A EXR608A Infiniium EXR 系列示波器&#xff1a;6 GHz&#xff0c;8 通道 全部 8 个通道均可提供 6 GHz 的带宽&#xff0c;…

NVLink 和 NVLink Switch

高速、多 GPU 通信的基础模组,助力将大型数据集更快地输入模型并在 GPU 之间快速交换数据。 文章目录 前言一、简介二、NVLink 性能三、NVLink Switch1. 通过 NVLink 通信提高 GPU 吞吐量2. NVIDIA NVLink 交换机四、NVLink Switch规格1. 通过完全连接实现非凡性能2. 功能强大…

智慧出行:数字孪生三维可视化大屏交通管控系统

智慧出行已成为现代城市发展的重要支柱&#xff0c;数字孪生技术在交通领域的应用不断深化。结合三维可视化技术&#xff0c;打造功能强大的大屏交通管控系统&#xff0c;将对城市交通管理带来巨大变革。该系统通过实时监测和数据分析&#xff0c;提供精准的交通状况显示和预测…

湖南(消费者调研)源点咨询 市场调研中关于定性调研的释义

湖南&#xff08;市场洞察&#xff09;源点咨询认为&#xff1a;定性调研是有关大量、详细信息的描述、领会和见解&#xff0c;而不仅仅是一种测量。它不像定量调研那么肤浅&#xff0c;它能提供更多有效的数据。定性调研旨在透过表面现象&#xff0c;超越所谓的"首要&quo…

速度白嫖:Minimax海螺上线图生视频功能

一、什么是Minimax海螺 网址&#xff1a;https://hailuoai.video/ Minimax海螺是一款创新的内容创作工具&#xff0c;专注于将静态图像转化为动态视频。它利用先进的图像处理与生成算法&#xff0c;帮助用户将普通图片迅速转变为引人入胜的短视频&#xff0c;适合社交媒体、…

Java | Leetcode java题解之第458题可怜的小猪

题目&#xff1a; 题解&#xff1a; class Solution {public int poorPigs(int buckets, int minutesToDie, int minutesToTest) {if (buckets 1) {return 0;}int[][] combinations new int[buckets 1][buckets 1];combinations[0][0] 1;int iterations minutesToTest /…