08-D3.js散点图

散点图.png

随机数+坐标轴+散点图

//Width and height
var w = 1000;
var h = 300;
var padding = 30;

//Dynamic, random dataset
var dataset = [];                   //Initialize empty array
var numDataPoints = 50;             //Number of dummy data points to create
var xRange = Math.random() * 1000;  //Max range of new x values
var yRange = Math.random() * 1000;  //Max range of new y values
for (var i = 0; i < numDataPoints; i++) {                   //Loop numDataPoints times
    var newNumber1 = Math.floor(Math.random() * xRange);    //New random integer
    var newNumber2 = Math.floor(Math.random() * yRange);    //New random integer
    dataset.push([newNumber1, newNumber2]);                 //Add new number to array
}

//Create scale functions
var xScale = d3.scaleLinear()
    .domain([0, d3.max(dataset, function (d) { return d[0]; })])
    .range([padding, w - padding * 2]);

var yScale = d3.scaleLinear()
    .domain([0, d3.max(dataset, function (d) { return d[1]; })])
    .range([h - padding, padding]);

var aScale = d3.scaleSqrt()
    .domain([0, d3.max(dataset, function (d) { return d[1]; })])
    .range([0, 10]);

//Define X axis
var xAxis = d3.axisBottom()
    .scale(xScale)
    .ticks(5);

//Define Y axis
var yAxis = d3.axisLeft()
    .scale(yScale)
    .ticks(5);

//Create SVG element
var svg = d3.select("body")
    .append("svg")
    .attr("width", w)
    .attr("height", h);

//Create circles
svg.selectAll("circle")
    .data(dataset)
    .enter()
    .append("circle")
    .attr("cx", function (d) {
        return xScale(d[0]);
    })
    .attr("cy", function (d) {
        return yScale(d[1]);
    })
    .attr("r", function (d) {
        return aScale(d[1]);
    });

//Create X axis
svg.append("g")
    .attr("class", "axis")
    .attr("transform", "translate(0," + (h - padding) + ")")
    .call(xAxis);

//Create Y axis
svg.append("g")
    .attr("class", "axis")
    .attr("transform", "translate(" + padding + ",0)")
    .call(yAxis);
    
  • 更新散点图,支持数据更新和动态比
更新散点图.gif

单击上方的文本可以生成新数据并更新图表

更新数据后,使用了动画过渡

更新x和y轴的比例尺

<p>单击此文本以使用新数据值更新图表</p>
//Width and height
var w = 500;
var h = 300;
var padding = 30;

//Dynamic, random dataset
var dataset = [];                                           //Initialize empty array
var numDataPoints = 50;                                     //Number of dummy data points to create
var maxRange = Math.random() * 1000;                        //Max range of new values
for (var i = 0; i < numDataPoints; i++) {                   //Loop numDataPoints times
    var newNumber1 = Math.floor(Math.random() * maxRange);  //New random integer
    var newNumber2 = Math.floor(Math.random() * maxRange);  //New random integer
    dataset.push([newNumber1, newNumber2]);                 //Add new number to array
}

//Create scale functions
var xScale = d3.scaleLinear()
    .domain([0, d3.max(dataset, function (d) { return d[0]; })])
    .range([padding, w - padding * 2]);

var yScale = d3.scaleLinear()
    .domain([0, d3.max(dataset, function (d) { return d[1]; })])
    .range([h - padding, padding]);

//Define X axis
var xAxis = d3.axisBottom()
    .scale(xScale)
    .ticks(5);

//Define Y axis
var yAxis = d3.axisLeft()
    .scale(yScale)
    .ticks(5);

//Create SVG element
var svg = d3.select("body")
    .append("svg")
    .attr("width", w)
    .attr("height", h);

//Create circles
svg.selectAll("circle")
    .data(dataset)
    .enter()
    .append("circle")
    .attr("cx", function (d) {
        return xScale(d[0]);
    })
    .attr("cy", function (d) {
        return yScale(d[1]);
    })
    .attr("r", 2);

//Create X axis
svg.append("g")
    .attr("class", "x axis")
    .attr("transform", "translate(0," + (h - padding) + ")")
    .call(xAxis);

//Create Y axis
svg.append("g")
    .attr("class", "y axis")
    .attr("transform", "translate(" + padding + ",0)")
    .call(yAxis);

d3.select("p")
    .on("click", function () {

        //New values for dataset
        var numValues = dataset.length;
        var maxRange = Math.random() * 1000;
        dataset = [];
        for (var i = 0; i < numValues; i++) {
            var newNumber1 = Math.floor(Math.random() * maxRange);
            var newNumber2 = Math.floor(Math.random() * maxRange);
            dataset.push([newNumber1, newNumber2]);
        }

        //Update scale domains
        xScale.domain([0, d3.max(dataset, function (d) { return d[0]; })]);
        yScale.domain([0, d3.max(dataset, function (d) { return d[1]; })]);

        //Update all circles
        svg.selectAll("circle")
            .data(dataset)
            .transition()
            .duration(1000)
            .attr("cx", function (d) {
                return xScale(d[0]);
            })
            .attr("cy", function (d) {
                return yScale(d[1]);
            });

        //Update X axis
        svg.select(".x.axis")
            .transition()
            .duration(1000)
            .call(xAxis);

        //Update Y axis
        svg.select(".y.axis")
            .transition()
            .duration(1000)
            .call(yAxis);
    });
  • 加载动画在过渡开始和结束时执行操作

//Update all circles
svg.selectAll("circle")
    .data(dataset)
    .transition()
    .duration(1000)
    // 在过渡开始时执行
    .on("start", function () {
        d3.select(this) // 选择 'this',即当前元素
      // .duration(150) //不能在这里添加新的过渡动画,
            .attr("fill", "magenta")
            .attr("r", 3)
    })
    .attr("cx", function (d) {
        return xScale(d[0]);
    })
    .attr("cy", function (d) {
        return yScale(d[1]);
    })
    // 在过渡结束时执行
    .on("end", function () {
        d3.select(this)
            .attr("fill", "black")
            .attr("r", 2)
    })

在默认情况下,任何元素在任意时刻都只能有一个过渡效果。新过渡效果会打断并覆盖原来的过渡效果;

与jQuery不同,默认情况下,jQuery 会把所有过渡效果排成队列,然后一个接一个地执行它们。换句话说,执行新过渡不会自动中断原有过渡。这种设计有时候会导致令人讨厌的界面行为,比如鼠标放到菜单上再离开后,菜单并不会马上就淡出,而是必须完全淡入之后再淡出。

由于过渡中存在的这个问题,一定要记住只能在 ("start", ...) 里面执行立即变换,而不能再添加任何过渡效果。

  • 优雅收场

在执行("end", ...) 的时候,主过渡已经结束了,因此再执行新过渡不会产生任何副作用。

.on("end", function () {
    d3.select(this)
        .transition() //新增
        .duration(1000) //新增
        .attr("fill", "black")
        .attr("r", 2);
});

单击 p 元素的文本,会发现:

圆形立即变成粉红并增大;

圆形过渡到新位置;

圆形过渡到原来的颜色和大小。

  • 或者顺序地使用连缀的方式
svg.selectAll("circle")
    .data(dataset)
    .transition() //过渡1
    .duration(1000)
    .on("start", function() {
        d3.select(this)
            .attr("fill", "magenta")
            .attr("r", 7);
    })
    .attr("cx", function(d) {
        return xScale(d[0]);
    })
    .attr("cy", function(d) {
        return yScale(d[1]);
    })
    .transition() //过渡2
    .duration(1000)
    .attr("fill", "black")
    .attr("r", 2);
  • 在剪切路径中包含可见元素

好在 SVG 支持剪切路径,也就是 Photoshop 或 Illustrator 中的蒙 版。剪切路径就是一个 SVG 元素,可以包含可见的元素,并与这个可见元素一起构成可以应用到其他元素的剪切路径或蒙版。在把蒙版应用到某个元素时,只有落在该蒙版图形内部的像素才会显示。

与 g 元素相似,clipPath 本身也不可见,但它可以包含可见的元素(这些元素用作蒙版)

<clipPath id="chart-area">
  <rect x="30" y="30" width="410" height="240"></rect>
</clipPath>

注意外面的 clipPath 元素有一个 ID,值为 chart-area。后面会通过这个 ID 来引用它。这个剪切路径内部有一个矩形,将被用作蒙版。

  • 使用剪切路径的步骤如下:
  1. 定义clipPath并给它一个ID

  2. 在这个clipPath中放一个可见的元素(通常是一个 rect,但也可以包含

    circle 和其他可见元素);

//定义剪切路径
svg.append("clipPath")//创建新的clipPath元素
    .attr("id", "chart-area")//为它指定ID,后面绑定时使用
    .append("rect")//在clipPath中,创建并添加新的rect
    .attr("x", padding) //设置rect的位置和大小
    .attr("y", padding)
    .attr("width", w - padding * 3)
    .attr("height", h - padding * 2);

我们希望把这个蒙版应用给所有圆形,可以分别为每个圆形都添加一个对该clipPath 的引用。但把所有圆形都放到一个分组 g 中,然后给这个分组添加引用, 会让代码更清晰也更简单:

  1. 在需要使用蒙版的元素上添加一个对 clipPath 的引用。
//创建圆形
svg.append("g") //创建新的g元素,包裹所有的circle
    .attr("id", "circles")  //指定它的ID为circles
    .attr("clip-path", "url(#chart-area)") //添加对 clipPath 的引用
    .selectAll("circle") 
    .data(dataset)
    ……

这个 g 元素的 clip�path 属性指向了新创建的剪切路径,语法有点不太常见:url(#chart-area)。但这都是 SVG 标准规定的。

clipPath.png

最终结果就是,当圆形跑到图表区域的边界时,超出边界的部分会被剪切掉。注意最上方和最右边的那些圆形。

image.png
<p>Click on this text to update the chart with new data values as many times as you like!</p>
//Width and height
var w = 500;
var h = 300;
var padding = 30;

//Dynamic, random dataset
var dataset = [];                                           //Initialize empty array
var numDataPoints = 50;                                     //Number of dummy data points to create
var maxRange = Math.random() * 1000;                        //Max range of new values
for (var i = 0; i < numDataPoints; i++) {                   //Loop numDataPoints times
    var newNumber1 = Math.floor(Math.random() * maxRange);  //New random integer
    var newNumber2 = Math.floor(Math.random() * maxRange);  //New random integer
    dataset.push([newNumber1, newNumber2]);                 //Add new number to array
}

//Create scale functions
var xScale = d3.scaleLinear()
    .domain([0, d3.max(dataset, function (d) { return d[0]; })])
    .range([padding, w - padding * 2]);

var yScale = d3.scaleLinear()
    .domain([0, d3.max(dataset, function (d) { return d[1]; })])
    .range([h - padding, padding]);

//Define X axis
var xAxis = d3.axisBottom()
    .scale(xScale)
    .ticks(5);

//Define Y axis
var yAxis = d3.axisLeft()
    .scale(yScale)
    .ticks(5);

//Create SVG element
var svg = d3.select("body")
    .append("svg")
    .attr("width", w)
    .attr("height", h);

//Define clipping path
svg.append("clipPath")
    .attr("id", "chart-area")
    .append("rect")
    .attr("x", padding)
    .attr("y", padding)
    .attr("width", w - padding * 3)
    .attr("height", h - padding * 2);

//Create circles
svg.append("g")
    .attr("id", "circles")
    .attr("clip-path", "url(#chart-area)")
    .selectAll("circle")
    .data(dataset)
    .enter()
    .append("circle")
    .attr("cx", function (d) {
        return xScale(d[0]);
    })
    .attr("cy", function (d) {
        return yScale(d[1]);
    })
    .attr("r", 2);

//Create X axis
svg.append("g")
    .attr("class", "x axis")
    .attr("transform", "translate(0," + (h - padding) + ")")
    .call(xAxis);

//Create Y axis
svg.append("g")
    .attr("class", "y axis")
    .attr("transform", "translate(" + padding + ",0)")
    .call(yAxis);



//On click, update with new data            
d3.select("p")
    .on("click", function () {

        //New values for dataset
        var numValues = dataset.length;                             
        var maxRange = Math.random() * 1000;                    
        dataset = [];                                               
        for (var i = 0; i < numValues; i++) {                   
            var newNumber1 = Math.floor(Math.random() * maxRange);
            var newNumber2 = Math.floor(Math.random() * maxRange);  
            dataset.push([newNumber1, newNumber2]);                 
        }

        //Update scale domains
        xScale.domain([0, d3.max(dataset, function (d) { return d[0]; })]);
        yScale.domain([0, d3.max(dataset, function (d) { return d[1]; })]);

        //Update all circles
        svg.selectAll("circle")
            .data(dataset)
            .transition()
            .duration(1000)
            .on("start", function () {
                d3.select(this)
                    .attr("fill", "magenta")
                    .attr("r", 7);
            })
            .attr("cx", function (d) {
                return xScale(d[0]);
            })
            .attr("cy", function (d) {
                return yScale(d[1]);
            })
            .on("end", function () {
                d3.select(this)
                    .transition()
                    .duration(1000)
                    .attr("fill", "black")
                    .attr("r", 2);
            });

        //Update X axis
        svg.select(".x.axis")
            .transition()
            .duration(1000)
            .call(xAxis);

        //Update Y axis
        svg.select(".y.axis")
            .transition()
            .duration(1000)
            .call(yAxis);
    });
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容

  • @(HTML5)[canvas与SVG] [TOC] 十一 、SVG HTML体系中,最常用的绘制矢量图的技术是S...
    踏浪free阅读 4,549评论 0 2
  • SVG 可伸缩矢量图形(Scalable Vector Graphics) 使用 XML 格式定义图像 是w3c的...
    小樓me阅读 833评论 0 1
  • 01-基础 按钮[https://developer.apple.com/design/human-interfa...
    dddt01阅读 2,138评论 0 21
  • d3 (核心部分)选择集d3.select - 从当前文档中选择一系列元素。d3.selectAll - 从当前文...
    谢大见阅读 3,424评论 1 4
  • 一、UI设计概述 UI相关术语解释 UI(User Interface)用户界面 GUI(Graphical Us...
    噗二爷阅读 1,344评论 0 6