Categorías: Todo - domain - format - scale - dates

por Piotr Delikat hace 9 años

17617

D3.js

Working with D3.js involves transforming date strings from your data into JavaScript date objects for effective visualization. The process starts with defining a date format using d3.

D3.js

D3.js

Converting visualisation into angular app

how to design it
we can alter the visualisation by writing a custom filters

app.filter('startDate', function(){ ... });

every visualisation element should be write as a custom derective

<html>
<head>
<script>

...

app.directive('d3Map', function(){ ... });

app.directive('d3LineChart', function(){ ... });

app.directive('d3ScatterPlot', function(){ ... });

app.directive('d3ChordDiagram', function(){ ... });

</script>

</head>

<body>

<d3-map></d3-map>

<d3-line-chart data="data"></d3-line-chart>

<d3-scatter-plot data="data"></d3-scatter-plot>

<d3-chord-diagram data="data"></d3-chord-diagram>

</body>

</html>

in HTML

app.directive('d3ChordDiagram', function(){ ... });

making visualisation responsive
inside controller we have to tell angular to watch the widow changes

angular.element($window).on('resize', function(){ $scope.$apply() })

inside chart code

scope.$watch(function() { return el.clientWidth * el.clientHeight}, function() { width = el.clientWidth height = el.clientHeight })

all the variables that depends on with and height goes inside secon function

height: 100%;
with: 100%;
inside chart element ng-style="{width: 100 / charts.length + '%' }"
creating controller for the chart
app.controller("ChartNameController", function($scope) { //code inside })

$scope.charts = d3.range(10).map(function() { return d3.range(10).map(Math.random) })

random data with 10 values for 10 charts

Creating directive with chart code
write css on the element not a class of the element
make an derective (inside an app) where code for a chart goes

app = directive('chartName', function() { })

it should return an link

return { link:link, restrict: 'E', scope: {data: '=' } };

function link (scope, el) { //d3 code inside, el is an element }

you can pull the data out the chart function

var data = scope.data;

it reference to the in html

make an chart element inside html
connect app into html
make an app

var app = angular.module("myApp", []);

link to angular libarary of course

Events

.on("mousemove", callbackFunction())
Draging
specyfi the dragStarted and dragged functions

function dragged(d) { d.x = d3.event.x; d.y = d3.event.y; d3.select(this) .attr("transform", 'translate(' + d.x + ',' + d.y + ')'); date = xScale.invert(d3.event.x); d.DATE = parseTime(date); temp = yScale.invert(d3.event.y); d.TMAX = temp.toString(); console.log(d); };

in this function we acctually are changing the data in the database!

we change the d.y, and d.x, and set it equal to the values from the event (mouse position) d3.event.x and d3.event.y

function dragStarted() { d3.event.sourceEvent.stopPropagation(); d3.select(this) .select('circle') .style("fill", 'red'); };

d3.event.sourceEvent.stopPropagation();

the event stops in the element that the event was registered

and the event is not sending to other DOM elements

it stops all other actions triggered by the event

is to prevent so called "bubbling up"

add a .call() to the parent element

dots.call(circleDrag);

what is the same as circleDrag(dots);

specify new behaviour

var circleDrag = d3.behavior.drag() .on("dragstart", dragStarted) .on("drag", dragged);

there is also .on("dragend", callBackFunction())

Zoom and Pan bahaviour
and we need a zoomed() function

function zoomed() { viz.attr("transform", "translate(" + d3.event.translate + ")" + "scale(" + d3.event.scale + ")"); };

d3.event.scale tells how low or high we scaled by mouse wheel

is bounder by .scaleExtent[1, 10]

d3.event.translate holds the mouse position during zooming

we need to .call(zoom); on the svg element

var svg = d3.select("#viz-wrapper") .append('svg') .attr('height', height + padding * 2 ) .attr('width', width + padding * 2) .call(zoom);

var viz = svg.append('g') .attr('id', 'viz') .attr('transform', 'translate(' + padding + ',' + padding + ')');

we append rest of elements to the svg, under id of 'viz'

var zoom = d3.behavior.zoom() .scaleExtent([1, 10]) .on("zoom", zoomed);

.on("zoom", zoomed);

it can be also "click", "double-click", or any other event

on the event "zoom", the zoomed function should be run

.scaleExtend()

limit how far we can zoom in and out

affect elements in the svg
work on mouse and touch events
expanding circles on mouseenter() with r accorrding to the data
to reser radious to default on mouseleave()

dots.on("mouseleave", function(d, i) { dot = d3.select(this); dot.select('circle') .attr('r', defaultCircleRadius); });

and we need to change radius of selected g child element based on d.TMAX on mouseenter()

dots.on("mouseenter", function(d, i) { dot = d3.select(this); radius = solveForR( parseInt(d.TMAX) ) dot.select('circle') .attr('r', rScale( radius )); });

we need to set a domain for r scale

rDomain = d3.extent(data, function(element){ return solveForR(parseInt(element.TMAX)); });

we use solveForR() function to calculate r based on TMAX

we represent area of circle, not the radius itself, so we need some math

var solveForR = function(TMAX) { // area of a circle // Area = Pi * r * r Area = Math.abs( TMAX ); r = Math.sqrt( Area / Math.PI); return r };

area based on TMAX value

set the range of circles expansion

var rScale = d3.scale.linear() .range([5, 50]);

set the dafault radius

var defaultCircleRadius = 2;

displaying text on mouseenter()
to hide them on mouseleave()

dots.on("mouseleave", function(d, i) { d3.select(this) .select('text') .style('display', 'none'); });

dots.on("mouseenter", function(d, i) { dot = d3select(this); dot.select('text') .style('display', 'block'); });

you can make sub-selection on text of children of the g element, and change it's style

.on("eventType", eventListener function() {});

and i is index of the selected element

eventListener function has acces to the data of selected element

mouseover() is also possible

styling

using and styling g element
then you can bind data

dots.attr('transform', function(d) {
//get the x position
date=parseTime.parse(d.DATE);

x = xScale(date)
//get the y position
y = yScale(d.TMAX)
return 'translate(' + x + ',' + y + ')'
})

and style it

.style('stroke', '#00ffd2')
.style('fill', '#006bff');



append circle to the g points

dots.append('circle')
.attr('r', 5);

and text

dots.append('text')
.text(function(d) {
return d.TMAX

});

});

.style('display', 'none');

to hide it on default

312

we can apend data to g element instead of the circles

dots = viz.selectAll('g.dots')

.data(data).enter().append('g').attr('class', 'dots');

Axis
styling the axis

to spread axis with a buffer

easy way

xMax = d3.max(data, function() { time = parseTime.parse(element.DATE); time.setMonth(getMonth() +1); return time });

oneDayLater = function(date) {return date.setDate(date.getDate() + 1)};

xScale.domain([xMin, xMax]);

xMin = d3.min(data, function(element) { time = parseTime.parse(element.DATE); time.setMonth(getMonth() - 1); return time });

oneDayEarlier = function(date) { return date.setDate(date.getDate() - 1) };

yDomain = d3.extent(data, function() { return parseInt(element.TMAX) * 1.1 });

.attr("dy", "10px");

shift along the y-axis on the position of an element or its content

.attr("dx", "-10px")

shift along the x-axis on the position of an element or its content

.style('font-size', '10px')

.style("text-anchor", "end")

align for the text (start-, middle- or end-alignment) a string of text relative to a given point

.attr("transform", function() { return "rotate(-65)" })

we use function to rotate each element in the selection and not all the elements

rotate() is much like translate() but it rotate the text not move it

rotating the labels

.selectAll('text')

var yAxis = d3.svg.axis()

.ticks(20);

.orient("left")

.scale(yScale)

var xAxis = d3.svg.axis()

adding yAxis

dont need to be transformed becouse it starts at the beginning of the svg (0)

.call(yAxis);

.attr("class", "y axis")

adding xAxis

other way

xAxis(viz.append("g").attr('class', 'x axis') )

svgElementId.append("g")

.call(xAxis);

.attr("transform", "translate(0," + height + ")")

translate() moves the text accross the axis

it tells the axis that values should go from 0 to height value

.attr("class", "x axis")

.ticks(8);

how many lables will be attached to axis

.orient("bottom")

where to laloyt the axis

.scale(xScale)

how to spread the axis

Give the meaningful context to every visualisation
time scale
.thickFormat(d3.time.format("%b %y"));
time formating

d3.time.format("%b %y");

to specify the domain base on time

xDomain = d3.extend(data, function(element) { return parseTime.parse(element.DATE) });

you can pass dates to d3.min and d3.max

but there is d3.extend(); method

you need to transform dates (strings of time values) from your data to an JavaScript date object

function createDate(dateString) { // create a formatter based on how we expect // the data from our data set var format = d3.time.format("%Y%m%d"); // create a JavaScript data object based on // the string return format.parse(dateString); };

function ready to do it

to use this format to transform valentinesDay data

parseTime.parse(valentinesDay);

it returns: Sat Feb 14 2015 00:00:00 GTM-0800 (PST)

.parse();

method that changes strings of time data into JS data objects

var parseTime = d3.time.format("%Y%m%d");

you can also use it to convert JavaScript data object into this format

parseTime(new Date());

return: "20150828"

we receive: valentinesDay = "20150214";

in d3.time.format(); you can specify the data format that you will recieve from your data

domain should be JavaScript date object
seting up minimum and maximum values for x and y scales

yScale.invert(100)

to check what value (from the data) is mapped to 100px space on y Axis

it can be usefull to track the data by mouse and touch events

DATE ex.

xScale.invert(800);

return: Tue Dec 31 1974 00:00:00 GMT+0100 (Środkowoeuropejski czas stand.)

xScale.invert(0);

return: Wed Aug 01 1973 00:00:00 GMT+0200 (Środkowoeuropejski czas letni)

TMAX ex.

yScale.invert(800);

return -33

yScale.invert(0);

return 361

yScale.domain(yDomain);

or change the domain if you put in new parameters

to see array of min and max values from the data

yScale.range()

to see available range in the svg for spreading data on Y axis

yScale(d.TMAX)

it will return the pixel value where one of the dots will be positioned inside the scale

dots.attr('cy', function(d) {
return yScale(d.TMAX) })

and map the data between these min and max values

this method contain min and max values from yDomain

domain

or you can do d3.max and d3.min simultaniously

yDomain = d3.extent(data, function(element) return parseInt(element.TMAX) });

it return an array with Min and Max values

you need such format to yScale method

so: yScale.domain(yDomain)

[-33, 361]

give you the range ofvalues from data parameter: TMAX

solution with better performance because you iterate the data only once

yMax = d3.max(data, function(element) { return parseInt(element.TMAX) });

will return maximum value from given data parameter: TMAX

yMin = d3.min(data, function(element) {return parseInt(element.TMAX) });

will return minimum value from given data parameter: TMAX

use parseInt on values from the data to convert it into numeric values

if you now that the data is numeric you can use

d3.max(data, function(element) { return +element.TMAX });

it depends on min and max values from your data

scale
.time()

var xScale = d3.time.scale().range([0, width])

.linear()

var yScale = d3.scale.linear().range([height, 0])

.range([ min , max ])

var height = 800;

var yScale = d3.scale.linear()
.range([height, 0]);

Scale define available pixel scapce of the visualisation
types

ordinary

sets of names, categories etc.

alphabetical

to deal with order of things


var x = d3.scale.ordinal()
.domain(["A", "B", "C", "D", "E", "F"]) .range([0, 1, 2, 3, 4, 5]);

you need to set a range for the order

.rangeBands([0, width]);

If width is 960, x("A") is now 0 and x("B") is 160, and so on.

quantative

pow

log

linear

to deal with numbers

Subtopic

multuple selection? make an object...
d3.select("body").style({"background-color": "black", "font-size" : "2.2em"});
.style('CSSparameter', 'CSSvalue')
much like jQuery

methods

debbuger in chrome dev tools
you can write "debugger" into your code, and app will stop there
Sources tab are really helpful with debbuging

You can open any files, and set break points to run your code to a specyfic line

and later access the variable or function in the given time

to explore what data is bind to where

by accessing d in the console

text()
math
Math.max(0, 10)

{return Math.max(0 + padding, Math.random() * width - padding)} //give us some random number based on width minus padding left and right

=== 10

gives you maximum value from equation

Math.abs()

return a absolute value (when you dealing with nagative numbers)

.attr('attribute', 'value')
value is offten a function

it can takes 2 parameters

i - index value of the data

optional

d - data or datum


dots.attr('r', function(d) {return d.TMAX});

for example takes one piece of data, and bind it to "r" attr of a circle
gives HTML attribute to the selected svg element
.append()
figure

.append('circle')

svg

.append('svg').attr('width', 200px).attr('height', 200px)

.append returns a new selection
it will append svg element to the current selections
data binding
Using dynamic properties in Selections

we can also set the properties depending on index i value

circles.style('stroke-width', function(d, i) { return i*2; }) ;

circles.attr('r', function(d, i) { return d; });

you can also access data from the array of objects

.attr('fill', function(d, i) { return d.color; })

Set the fill color depending of the bound object

circles.attr('r', function(d, i) { return d.r; });

var data = [
{cx:50, cy:50, r: 10, color: '#ff0000'},
{cx:150, cy:50, r: 20, color: '#ff0066'},

{cx:250, cy:50, r: 30, color: '#ff00aa'},

{cx:350, cy:50, r: 40, color: '#ff00ff'}

];

var viz = d3.select("#viz-wrapper")

.append('svg')

.attr('id', 'viz');

d3.csv('../../app/climate_data.csv', function(data) {

dots = viz.selectAll('circle') //ghost selection

dots.data(data) //return 516 objects

dots.enter() // still return 516 objects

dots.append('circle'); //adding 516 circle elements to HTML




//binding data (absolute value from max temp/ 100 to make it C deg) to r of a circle


dots.attr('r', function(d) {return Math.abs(d.TMAX) / 100});

});



enter()

we can treat the .enter() selection just like any other selection to modify the content.

specifies the selection

prepares the selection to update the DOM with new elements

return data that are already binded to the DOM element + new data

If the new dataset is larger, the surplus data ends up in the enter selection and new nodes are added.

if we are creating ghost selection it will return same number of elements as the data() operator

in case that selected elements have some values new data will not replace it

.data(values[, key])

<script type="text/javascript">

// Create a data array

var data = [10, 20, 30, 40];

// Bind data array to the Selection

var circles = d3.selectAll('circle').data(data);

console.log(circles.data());

// [10, 20, 30, 40]

</script>

key is additional parameter

you can call a function there, that interacts with every data item

it returns the number of elements = number of data records

for binding data from file to choosen element

also...after selecting DOM element you have access to the data that is bind to selected element
ghost selection | empty selection

you can select element that not exist yet

methods of binding data to selected element

core of D3

request()
D3 Data Formats

d3.xml()

d3.tsv()

d3.html()

d3.text()

d3.csv('filePath', function(error, data){})

<script type="text/javascript">
d3.csv('climate_data.csv', function(data) {
debugger;
});
</script>

d3 converting cvs data to JSON like objects

after request type "data" in the console to access it

awesome!

d3.json('filePath', function(error, data){})

d3.json('data/phones.json', function(error, data) {

//all the magic whit data here

conslole.log(error);

console.log(data);

});

data is a data from a file

result of the request

error is a error message that shows up when the file can't be accesed

function is a callback function

will work only if data is succesfully loaded, or fail

file name relative to the script file

asynchronius of course

non-blocking

.classed()
we can also give and deleted class of selected documents by this method

d3.selectAll('div').classed('hosue', false)

none of the divs will have class="house"

d3.selectAll('div').classed('house',true)

it will return a selection

all divs will have calss="house"

d3.select('div').classed('house')

return false becouse non all div has calss ('.hosue')

d3.select('#tree').classed('house')

it return false, becouse element (#tree) don't have class ('hosue')

return true of false depending on class attached to element

adding functions

selection.call(function[, arguments…])

<p>I like fruits.</p>
<p>Apple, Banana and Orange</p>

<script type="text/javascript">

function set_custom_attr(selection, attr, value) {
selection
.attr(attr, value);
}

d3.selectAll('p').call(set_custom_attr, 'align', 'center');
</script>

and the result is:

<p align="center">I like fruits.</p>
<p align="center">Apple, Banana and Orange</p>

you can call function that you have declared with arguments
.each(function)

d3.selectAll('p').each(function(d, i){
var self = d3.select(this);
// Output the text of every element

console.log(self.text());

});

</script>

The .each() method calls the function argument for each element inside the Selection

selections

you can select, HTML elements outside SVG, and change them with d3.style() etc.
selecting a class
d3.selectAll('.className')

jQuery way works :)

d3.selectAll('div.className')
d3.selectAll
d3.selectAll("p").style("color", "white");
d3.select
d3.select(#graph)
d3.select("body").style("background-color", "black");
like in jQuery
each D3 selection is simply an array of nodes.
takes any given CSS selector as a parameter

charts functions

d3.layout.pie()

steps

Exit
Transistion
Transform & Translate
Enter and Append
Select & Bind
Load

JSON

selecting
to check array lenght

arrayName.lenght

braces

objectName['keyName']

from array (0 based notation)

test.phones[0]

dot notation

objectName.nestedObjectName.keyName

Objects
Primitive Ojbects/Nested Objects
Keys:Values
Array of values
JavaScript Object Notation
Data Format of a modern web browser :)

SVG

text element
dy

is used to center it verticaly

do not support margins and paddings
aestetic
is changed by .style
geometry
is changed by .attr
g element
ex.

<svg width="100%" height="100%" viewBox="0 0 95 50"

xmlns="http://www.w3.org/2000/svg">

<g stroke="green" fill="white" stroke-width="5">

<circle cx="25" cy="25" r="15" />
<circle cx="40" cy="25" r="15" />
<circle cx="55" cy="25" r="15" />
<circle cx="70" cy="25" r="15" />

</g>

</svg>

you can use it to attach style or events to multiple elements (children of g element)
Transformations applied to the g element are performed on all of its child elements.
Is a contianer used to group objects
figures
line

stroke-width

stroke

color

x2, y2

x1, y1

circle

attributes

cy

cx

r

positioning
(negative X, negative Y)
(positiveX, positiveY)
xml format
scalable vector graphics

Much more like a library like jQuery, than a framework like angular.js

npm install d3
to use it in node.js (where lacking DOM) you can require some DOM implementations
like JSDOM

var d3 = require("d3"),

jsdom = require("jsdom");

var document = jsdom.jsdom(),
svg =d3.select(document.body).append("svg");

you can chain all the methods toogether
D3.js uses "document traversal" aka. DOM traversal
Focused od data Visualisation