En el laboratorio RMD-RCHARTS4 vimos como podríamos integrar gráficos que son desarrollados mediante la librería de rcharts. Como vimos rcharts trabaja como una librería del tipo paraguas, que quiere decir que la librería puede crear las visualizaciones basada en otras librerías de menor nivel. Por ejemplo:
El problema con este enfoque es que no hay uniformidad en las bondades o prestaciones que las librerías de nivel más bajo proveen, esto tiene como consecuencia que no es posible crear visualizaciones si la librería subyacente no soporta determinada funcionalidad
El problema general se parece a cuando escogemos un lenguaje de programación. Si seleccionamos un lenguaje de alto nivel tengo un nivel de abstracción mayor, pero no tengo el nivel de control a detalle. Mientras que si trabajo con un lenguaje de bajo nivel tengo mayor control aunque requiere un trabajo más detallado con mayor cantidad de instrucciones
Lo mismo ocurre con visualizaciones, puedo definir en ggplot un diagrama deseado con 4 o 5 lineas, pero el nivel de interactividad es limitado, por otro lado si trabajo a bajo nivel puedo implementar cualquier interactividad
La pregunta es entonces a que nivel deseo trabajar?.
Existen una gran cantidad de librerías gráficas de javascript.
D3.js (también conocido como D3) es una biblioteca de JavaScript para producir visualizaciones de datos dinámicas e interactivas en navegadores web. Hace uso de los estándares Scalable Vector Graphics (SVG), HTML5 y Cascading Style Sheets (CSS). Su desarrollo inició en 2011, cuando se lanzó la versión 2.0.0 . Con el lanzamiento de la versión 4.0.0 en junio de 2016, D3 pasó de ser una sola biblioteca a una colección de bibliotecas modulares más pequeñas que se pueden usar de forma independiente.[wikipedia]
Definimos como interactividad la capacidad del usuario final de alterar o personalizar en forma significativa la visualización sin tener conocimientos de elementos programáticos.
JavaScript utiliza un modelo de eventos en el que los “eventos” se desencadenan por cosas que suceden, como una nueva entrada del usuario, proporcionada a través de un teclado, un mouse o una pantalla táctil. La mayoría de las veces, los eventos se desencadenan constantemente, es solo que nadie los escucha, por lo que se ignoran. Para hacer que nuestras visualizaciones sean interactivas, definimos fragmentos de código que escuchan y reaccionan a eventos específicos que están asociados al DOM.
El DOM es básicamente un modelo jerárquico que comprende todos los objetos textuales y gráficos que estan dentro de una página web una vez que esta es recibida por el browser. Modelan tanto la ventana del navegador como el historial, el documento o página web, y todos los elementos que pueda tener dentro la propia página, como párrafos, divisiones, tablas, formularios y sus campos, etc. Por medio de Javascript, podemos acceder a estos elementos, para alterar sus propiedades, invocar a sus métodos, y eliminarlos de ser necesario
Crédito imagen :
Wikipedia
Los eventos JS son las acciones que ejecuta el usuario con el dispositivo apuntador,puede ser el mouse o un toque en pantallas táctiles. Existen múltiples eventos que podemos controlar conJavascript.
En D3 tenemos eventos similares que podemos controlar con la función on()
Los listeners son básicamente pedazos de código que se ejecutan cuando JavaScript siente que un evento se ha producido Ejemplo :
"d3.select('p')
.on('click', function() {
//Do something on click
});"
## [1] "d3.select('p')\n .on('click', function() {\n //Do something on click\n });"
D3 es más útil cuando se utiliza para generar y manipular imágenes como gráficos vectoriales escalables (SVG). Es posible dibujar con divs y otros elementos HTML nativos, pero es un poco accidentado y está sujeto a las inconsistencias habituales entre los diferentes navegadores. Usar SVG es más confiable, visualmente consistente y más rápido.
Antes de poder dibujar cualquier cosa, debe crear un elemento SVG. Piense en el elemento SVG como un lienzo en el que se representan sus imágenes. (En ese sentido, SVG es conceptualmente similar al elemento de CANVAS de HTML). Como mínimo, es bueno especificar los valores de ancho y alto. Si no los especifica, el SVG se comportará como un elemento HTML de nivel de bloque y ocupará tanto espacio como sea posible dentro de su elemento contenedor.
El siguiente diagrama explica la relación entre SVG y el DOM
SVG
NOTA: En los siguientes ejercicios no nos centraremos en las funciones de D3 , sino en como lograr la interactividad.
Cuando algún objeto gráfico es cambiado, como por ejemplo cuando aplicamos un filtro o cuando cargamos un nuevo set datos los cambios ocurren en forma inmediata. Sin embargo podemos hacer que dicha transición sea más animada de tal manera de dar un efecto de una aplicación “viva” por detrás. Las transiciones son controladas por la función transition Hay varios parámetros que afectan el comportamiento de la transición duration: que especifica el tiempo total que debe tomar la transición delay: el tiempo del cambio de cada subelemento( ej cada barra o cada pedazo de pastel) Adicionalmente la forma que esta transición ocurre se controla con la función ease() ejemplos: .ease(“linear”) .ease(“circle”) .ease(“elastic”) .ease(“bounce”)
## ```js
##
## <!DOCTYPE html>
## <html lang="en">
## <head>
## <meta charset="utf-8">
## <title>D3: Cargar un nuevo dataset </title>
## <script src="https://d3js.org/d3.v7.min.js"></script>
## <style type="text/css">
## /*No hay reglas de CSS */
## </style>
## </head>
## <body>
##
## <p>Dar click en este parrafo para recargar un nuevo dataset</p>
##
## <script type="text/javascript">
##
## //Deinimos alto y ancho de la visualizacion
## var w = 600;
## var h = 250;
##
## var dataset = [ 5, 10, 13, 19, 21, 25, 22, 18, 15, 13,
## 11, 12, 15, 20, 18, 17, 16, 18, 23, 25 ];
##
## var xScale = d3.scaleBand()
## .domain(d3.range(dataset.length))
## .rangeRound([0, w])
## .paddingInner(0.05);
##
## var yScale = d3.scaleLinear()
## .domain([0, d3.max(dataset)])
## .range([0, h]);
##
## //Creamos un objeto SVG
## var svg = d3.select("body")
## .append("svg")
## .attr("width", w)
## .attr("height", h);
##
## //Creamos el diagrama de Barras
## svg.selectAll("rect")
## .data(dataset)
## .enter()
## .append("rect")
## .attr("x", function(d, i) {
## return xScale(i);
## })
## .attr("y", function(d) {
## return h - yScale(d);
## })
## .attr("width", xScale.bandwidth())
## .attr("height", function(d) {
## return yScale(d);
## })
## .attr("fill", function(d) {
## return "rgb(0, 0, " + Math.round(d * 10) + ")";
## });
##
## //Creamos la etiquetas
## svg.selectAll("text")
## .data(dataset)
## .enter()
## .append("text")
## .text(function(d) {
## return d;
## })
## .attr("text-anchor", "middle")
## .attr("x", function(d, i) {
## return xScale(i) + xScale.bandwidth() / 2;
## })
## .attr("y", function(d) {
## return h - yScale(d) + 14;
## })
## .attr("font-family", "sans-serif")
## .attr("font-size", "11px")
## .attr("fill", "white");
##
##
##
##
## //Creamos el listener
## d3.select("p")
## .on("click", function() {
##
## //New values for dataset
## var numValues = dataset.length;
## dataset = [];
## for (var i = 0; i < numValues; i++) {
## var newNumber = Math.floor(Math.random() * 25);
## dataset.push(newNumber);
## }
##
## //Actualizamos el diagrama
## svg.selectAll("rect")
## .data(dataset)
## .transition()
## .delay(function(d, i) {
## return i / dataset.length * 1000;
## })
## .duration(500)
## .attr("y", function(d) {
## return h - yScale(d);
## })
## .attr("height", function(d) {
## return yScale(d);
## })
## .attr("fill", function(d) {
## return "rgb(0, 0, " + Math.round(d * 10) + ")";
## });
##
## //Actualizamos las etiquetas
## svg.selectAll("text")
## .data(dataset)
## .transition()
## .delay(function(d, i) {
## return i / dataset.length * 1000;
## })
## .duration(500)
## .text(function(d) {
## return d;
## })
## .attr("x", function(d, i) {
## return xScale(i) + xScale.bandwidth() / 2;
## })
## .attr("y", function(d) {
## return h - yScale(d) + 14;
## });
##
## });
##
##
## </script>
## </body>
## </html>
## ```
Para el siguiente gráfico vamos a crear un diagrama de dispersión por lo tanto la data debe ser arreglo de arreglos .Realizamos la siguientes tareas Creamos una área svg de 500x300 Creamos círculos de radio = 2 en las posiciones x,y generadas Creamos el contenedor g donde vamos a poner nuestros ejes Creamos el onclick sobre el elemento p , en esta función hacemos : Recreamos los escalas Para cada punto creado configuramos una transición que primero cambia el radio , el color, realiza la transición y vuelve al tamaño y color original la transición .
Por ultimo note que además debemos hacer una transición en los ejes
que también
deben ajustarse a los nuevo valores
Note que por defecto solo una transición puede estar activa a la vez , cuando hay varias transiciones el resultado es impredecible
## ```js
##
## <!DOCTYPE html>
## <html lang="en">
## <head>
## <meta charset="utf-8">
## <title>D3: Transiciones en diagram de dispersión </title>
## <script src="https://d3js.org/d3.v7.min.js"></script>
## <style type="text/css">
## /* NO uso CSS */
## </style>
## </head>
## <body>
## <p style="text-align:left">Este es un parrafo .</p>
## <p>Clik aquí para cargar un nuevo dataset!</p>
##
##
## <script type="text/javascript">
##
## //Definimos alto y ancho
## var w = 500;
## var h = 300;
## var padding = 30;
##
## //Recreamos el dataset
## var dataset = [];
## var numDataPoints = 50;
## var maxRange = Math.random() * 1000;
## for (var i = 0; i < numDataPoints; i++) {
## var newNumber1 = Math.floor(Math.random() * maxRange);
## var newNumber2 = Math.floor(Math.random() * maxRange);
## dataset.push([newNumber1, newNumber2]);
## }
##
## //Creamos las escalas para los ejes
## 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]);
##
## //Creamos el eje x
## var xAxis = d3.axisBottom()
## .scale(xScale)
## .ticks(5);
##
## //Creamos el eje y
## var yAxis = d3.axisLeft()
## .scale(yScale)
## .ticks(5);
##
## //Creamos el contenedor svg
## var svg = d3.select("body")
## .append("svg")
## .attr("width", w)
## .attr("height", h);
##
## //Creamos los puntos
## 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);
##
## //Incluimos el eje X
## svg.append("g")
## .attr("class", "x axis")
## .attr("transform", "translate(0," + (h - padding) + ")")
## .call(xAxis);
##
## //Incluimos el je Y
## svg.append("g")
## .attr("class", "y axis")
## .attr("transform", "translate(" + padding + ",0)")
## .call(yAxis);
##
##
##
## //El Listener
## d3.selectAll("p")
## .on("click", function() {
## 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]);
## }
##
## //Actualizamos escala
## xScale.domain([0, d3.max(dataset, function(d) { return d[0]; })]);
## yScale.domain([0, d3.max(dataset, function(d) { return d[1]; })]);
##
## //Actualizamos los puntos
## svg.selectAll("circle")
## .data(dataset)
## .transition()
## .duration(1000)
## .on("start", function() {
## d3.select(this)
## .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);
## });
##
## //Actualizamos X axis
## svg.select(".x.axis")
## .transition()
## .duration(1000)
## .call(xAxis);
##
## //Actualizamos Y axis
## svg.select(".y.axis")
## .transition()
## .duration(1000)
## .call(yAxis);
##
## });
##
##
## </script>
## </body>
## </html>
##
##
##
## "
Uno de los efectos interesantes es resaltar un objeto gráfico sobre el cual se esta trabajando. Existen varias formas de hacerlo. La siguiente es hacerlo mediante una sentencia de CSS, es simple solo con CSS, ¡no se requiere JavaScript! El selector de pseudoclase CSS :hover se puede usar en combinación con cualquier otro selector para seleccionar, el objeto, pero se activa cuando el mouse se desplaza sobre el elemento. Aquí, seleccionamos todos los rectángulos SVG y establecemos su relleno en naranja.
Esta función es básicamente realizada por el browser, por lo tanto algunas propiedades particulares de cada browser deberán ser definidas. Los parámetros para mozilla,Opera,y otros (webkit) como safari se encuentran en este código
## ```js
##
## <!DOCTYPE html>
## <html lang="en">
## <head>
## <meta charset="utf-8">
## <title>D3: Efecto Hover basado en CSS</title>
## <script src="https://d3js.org/d3.v7.min.js"></script>
## <style type="text/css">
##
## rect {
## -moz-transition: all 1s;
## -o-transition: all 0.3s;
## -webkit-transition: all 0.3s;
## transition: all 0.3s;
## }
##
## rect:hover {
## fill: orange;
## }
##
## </style>
## </head>
## <body>
## <script type="text/javascript">
##
## //Alto/ancho
## var w = 600;
## var h = 250;
##
## var dataset = [ 5, 10, 13, 19, 21, 25, 22, 18, 15, 13,
## 11, 12, 15, 20, 18, 17, 16, 18, 23, 25 ];
##
## var xScale = d3.scaleBand()
## .domain(d3.range(dataset.length))
## .rangeRound([0, w])
## .paddingInner(0.05);
##
## var yScale = d3.scaleLinear()
## .domain([0, d3.max(dataset)])
## .range([0, h]);
##
## // SVG
## var svg = d3.select("body")
## .append("svg")
## .attr("width", w)
## .attr("height", h);
##
## //Barras
## svg.selectAll("rect")
## .data(dataset)
## .enter()
## .append("rect")
## .attr("x", function(d, i) {
## return xScale(i);
## })
## .attr("y", function(d) {
## return h - yScale(d);
## })
## .attr("width", xScale.bandwidth())
## .attr("height", function(d) {
## return yScale(d);
## })
## .attr("fill", function(d) {
## return "rgb(0, 0, " + Math.round(d * 10) + ")";
## });
##
## //Etiquetas
## svg.selectAll("text")
## .data(dataset)
## .enter()
## .append("text")
## .text(function(d) {
## return d;
## })
## .attr("text-anchor", "middle")
## .attr("x", function(d, i) {
## return xScale(i) + xScale.bandwidth() / 2;
## })
## .attr("y", function(d) {
## return h - yScale(d) + 14;
## })
## .attr("font-family", "sans-serif")
## .attr("font-size", "11px")
## .attr("fill", "white");
##
## </script>
## </body>
## </html>
## ```
En el siguiente gráfico estamos cambiando el color de la barra una vez que estamos pasando sobre la misma, pero lo hacermos en forma programática mediante un listener on(mouseover) , y cuando salimos del reponemos el color original mediante otro listener on(mouseout) Para esto usaremos 2 listeners , mouseover y mounseout. En este caso para recuperar la barra seleccionada usaremos la función this
## ```js
##
##
## <!DOCTYPE html>
## <html lang="en">
## <head>
## <meta charset="utf-8">
## <title>D3: Using D3 to change fill color on mouseover AND mouseout</title>
## <script src="https://d3js.org/d3.v7.min.js"></script>
## <style type="text/css">
## /* No css en este caso */
## </style>
## </head>
## <body>
## <script type="text/javascript">
##
## //Anch y alto
## var w = 600;
## var h = 250;
##
## var dataset = [ 5, 10, 13, 19, 21, 25, 22, 18, 15, 13,
## 11, 12, 15, 20, 18, 17, 16, 18, 23, 25 ];
##
## var xScale = d3.scaleBand()
## .domain(d3.range(dataset.length))
## .rangeRound([0, w])
## .paddingInner(0.05);
##
## var yScale = d3.scaleLinear()
## .domain([0, d3.max(dataset)])
## .range([0, h]);
##
## //SVG
## var svg = d3.select("body")
## .append("svg")
## .attr("width", w)
## .attr("height", h);
##
## //Barras
## svg.selectAll("rect")
## .data(dataset)
## .enter()
## .append("rect")
## .attr("x", function(d, i) {
## return xScale(i);
## })
## .attr("y", function(d) {
## return h - yScale(d);
## })
## .attr("width", xScale.bandwidth())
## .attr("height", function(d) {
## return yScale(d);
## })
## .attr("fill", function(d) { //Listener 1
## return "rgb(0, 0, 125)";
## })
## .on("mouseover", function() { //Listener 2
## d3.select(this)
## .attr("fill", "orange");
## })
## .on("mouseout", function(d) {
## d3.select(this)
## .transition()
## .duration(1150)
## .attr("fill", "rgb(0, 0, 125)");
## console.log(d);
## });
##
## //Etiquetas
## svg.selectAll("text")
## .data(dataset)
## .enter()
## .append("text")
## .text(function(d) {
## return d;
## })
## .attr("text-anchor", "middle")
## .attr("x", function(d, i) {
## return xScale(i) + xScale.bandwidth() / 2;
## })
## .attr("y", function(d) {
## return h - yScale(d) + 14;
## })
## .attr("font-family", "sans-serif")
## .attr("font-size", "11px")
## .attr("fill", "white");
##
## </script>
## </body>
## </html>
## ```
Este lab tiene CSS: hover y un listener onclick. El primero para resaltar el objeto que se esta posicionando mientras que el onclick va a realizar un reordenamiento de los datos y lo hacemos mediante una variable lógica que indica el ordenamiento deseado
## ```js
##
##
##
## <!DOCTYPE html>
## <html lang="en">
## <head>
## <meta charset="utf-8">
## <title>D3:Animación y Ordenamiento </title>
## <script src="https://d3js.org/d3.v7.min.js"></script>
## <style type="text/css">
##
## rect:hover {
## fill: orange;
## }
##
## </style>
## </head>
## <body>
## <script type="text/javascript">
##
## //ANCHO Y ALTO
## var w = 600;
## var h = 250;
##
## var dataset = [ 5, 10, 13, 19, 21, 25, 22, 18, 15, 13,
## 11, 12, 15, 20, 18, 17, 16, 18, 23, 25 ];
##
## var xScale = d3.scaleBand()
## .domain(d3.range(dataset.length))
## .rangeRound([0, w])
## .paddingInner(0.05);
##
## var yScale = d3.scaleLinear()
## .domain([0, d3.max(dataset)])
## .range([0, h]);
##
## //Create SVG element
## var svg = d3.select("body")
## .append("svg")
## .attr("width", w)
## .attr("height", h);
##
## //Create bars
## svg.selectAll("rect")
## .data(dataset)
## .enter()
## .append("rect")
## .attr("x", function(d, i) {
## return xScale(i);
## })
## .attr("y", function(d) {
## return h - yScale(d);
## })
## .attr("width", xScale.bandwidth())
## .attr("height", function(d) {
## return yScale(d);
## })
## .attr("fill", function(d) {
## return "rgb(0, 0, " + Math.round(d * 10) + ")";
## })
## // Listener
## .on("click", function() { sortBars();sortdataset(); relabel(); });
##
## //Etiquetas
## svg.selectAll("text")
## .data(dataset)
## .enter()
## .append("text")
## .text(function(d) {
## return d;
## })
## .attr("text-anchor", "middle")
## .attr("x", function(d, i) {
## return xScale(i) + xScale.bandwidth() / 2;
## })
## .attr("y", function(d) {
## return h - yScale(d) + 14;
## })
## .attr("font-family", "sans-serif")
## .attr("font-size", "11px")
## .attr("fill", "white");
##
## //Control del ordenamiento
## var sortOrder = false;
##
## //Sorting
## var sortBars = function() {
## sortOrder = !sortOrder;
## svg.selectAll("rect")
## .sort(function(a, b) {
## if (sortOrder) {
## return d3.ascending(a, b);
## } else {
## return d3.descending(a, b);
## }
## })
## .transition()
## .duration(1000)
## .attr("x", function(d, i) {
## return xScale(i);
## });
## }; //end sortbars
##
## var sortdataset = function()
## {
## console.log("antes");
## console.log(dataset);
## if (sortOrder) {
## dataset.sort(function(a, b) { return a-b });
## } else {
## dataset.sort(function(a, b) { return b-a });
## }
## console.log("despues");
## console.log(dataset);
## };
##
##
## var relabel = function() {
## console.log("relabeling")
## var texts = svg.selectAll("text")
## texts.remove();
## svg.selectAll("text")
## .data(dataset)
## .enter()
## .append("text")
## .text(function(d) {
## return d;
## })
## .attr("text-anchor", "middle")
## .attr("x", function(d, i) { return xScale(i) + xScale.bandwidth() / 2; })
## .attr("y", function(d) { return h - yScale(d) + 14; })
## .attr("font-family", "sans-serif")
## .attr("font-size", "11px")
## .attr("fill", "white");
##
## };//end relabel
##
##
##
## </script>
## </body>
## </html>
## ```
La función de tooltip es muy común y permite presentar al usuario cualquier variable(s) que se puedan asociar a un elemento gráfico. La mayoria de paquetes gráficos permiten la configuración de valores de tooltip , pero generalmente deben estar dentro del mismo set de datos, puede ser la columna que se esta mostrando o una combinación de columnas.
En este ejemplo vamos a visualizar data que viene de otro dataset, sin necesidad de hacer funciones tipo join .
## ```js
##
##
## <!DOCTYPE html>
## <html lang="en">
## <head>
## <meta charset="utf-8">
## <title>D3: An SVG element tooltip</title>
## <script src="https://d3js.org/d3.v7.min.js"></script>
## <style type="text/css">
##
## rect:hover {
## fill: orange;
## }
##
## </style>
## </head>
## <body>
## <script type="text/javascript">
##
## //Ancho y alto
## var w = 600;
## var h = 250;
##
## var dataset = [ 5, 10, 13, 19, 21, 25, 22, 18, 15, 13,
## 11, 12, 15, 20, 18, 17, 16, 18, 23, 25 ];
##
## var dataset1 = [ "Guayaquil", "Quito","Santo Domingo","Machala",
## "Durán","Durán","Cayambe","Loja","Ambato","Esmeraldas","Riobamba","Ibarra","Sangolquí","Chone","Pasaje", "Latacunga","Babahoyo","Milagro","Milagro","Pujili"]
##
##
## var xScale = d3.scaleBand()
## .domain(d3.range(dataset.length))
## .rangeRound([0, w])
## .paddingInner(0.05);
##
## var yScale = d3.scaleLinear()
## .domain([0, d3.max(dataset)])
## .range([0, h]);
##
## //SVG
## var svg = d3.select("body")
## .append("svg")
## .attr("width", w)
## .attr("height", h);
##
## //Barras
## svg.selectAll("rect")
## .data(dataset)
## .enter()
## .append("rect")
## .attr("x", function(d, i) { return xScale(i);})
## .attr("y", function(d) {return h - yScale(d); })
## .attr("width", xScale.bandwidth())
## .attr("height", function(d) { return yScale(d); })
## .attr("fill", function(d) { return "rgb(0, 0, " + Math.round(d * 10) + ")"; })
## .attr("val", function(d, i ) { return (d + " "+dataset1[i]);})
## .on("mouseover", function(d) {
## var xPosition = parseFloat(d3.select(this).attr("x")) + xScale.bandwidth() / 2;
## var yPosition = parseFloat(d3.select(this).attr("y")) + 14;
## var valor = (d3.select(this).attr("val")) ;
## console.log("x: "+ xPosition)
## console.log ("y:"+ yPosition)
## console.log(valor)
## svg.append("text")
## .attr("id", "tooltip")
## .attr("x", xPosition)
## .attr("y", yPosition)
## .attr("text-anchor", "middle")
## .attr("font-family", "sans-serif")
## .attr("font-size", "11px")
## .attr("font-weight", "bold")
## .attr("fill", "black")
## .text(valor);
##
## })
## .on("mouseout", function() {
## //elimina tooltip
## d3.select("#tooltip").remove();})
##
## .on("click", function() { sortBars(); });
##
## //ordenamiento
## var sortOrder = false;
##
## //sortea
## var sortBars = function() {
##
## //Flip value of sortOrder
## sortOrder = !sortOrder;
##
## svg.selectAll("rect")
## .sort(function(a, b) {
## if (sortOrder) {
## return d3.ascending(a, b);
## } else {
## return d3.descending(a, b);
## }
## })
## .transition()
## .delay(function(d, i) {
## return i * 50;
## })
## .duration(1000)
## .attr("x", function(d, i) {
## return xScale(i);
## });
##
## };
##
## </script>
## </body>
## </html>
## ```
Comúnmente el usuario desea manipular directamente la data mediante eliminar o o aumentar registros.Esto puede ser un problema para el usuario final. en la siguiente visualización vamos a crear 2 párrafos que tienen asociados listeners que nos permiten incluir nuevo registro o eliminar registros. En este caso, y para simplificar, eliminaremos el primer registro e incluiremos registros al final, aunque la funcionalidad puede ser fácilmente expansible
## ```js
##
##
##
## <!DOCTYPE html>
## <html lang="en">
## <head>
## <meta charset="utf-8">
## <title>D3: Logging out selections from the data join process</title>
## <script src="https://d3js.org/d3.v7.min.js"></script>
## <style type="text/css">
## /* NO CSS */
## </style>
## </head>
## <body>
##
## <p id="add">Añadir</p>
## <p id="remove">Remover</p>
##
## <script type="text/javascript">
##
## //Ancho y alto
## var w = 600;
## var h = 250;
##
## var dataset = [ { key: 0, value: 5 }, //dataset es ahora un arreglo de objetoss.
## { key: 1, value: 10 }, //indexados '.
## { key: 2, value: 13 },
## { key: 3, value: 19 },
## { key: 4, value: 21 },
## { key: 5, value: 25 },
## { key: 6, value: 22 },
## { key: 7, value: 18 },
## { key: 8, value: 15 },
## { key: 9, value: 13 },
## { key: 10, value: 11 },
## { key: 11, value: 12 },
## { key: 12, value: 15 },
## { key: 13, value: 20 },
## { key: 14, value: 18 },
## { key: 15, value: 17 },
## { key: 16, value: 16 },
## { key: 17, value: 18 },
## { key: 18, value: 23 },
## { key: 19, value: 25 } ];
##
## var xScale = d3.scaleBand()
## .domain(d3.range(dataset.length))
## .rangeRound([0, w])
## .paddingInner(0.05);
##
## var yScale = d3.scaleLinear()
## .domain([0, d3.max(dataset, function(d) { return d.value; })])
## .range([0, h]);
##
## //Asociando
## var key = function(d) {
## return d.key;
## };
##
## //SVG
## var svg = d3.select("body")
## .append("svg")
## .attr("width", w)
## .attr("height", h);
##
## //Barras
## svg.selectAll("rect")
## .data(dataset, key)
## .enter()
## .append("rect")
## .attr("x", function(d, i) {
## return xScale(i);
## })
## .attr("y", function(d) {
## return h - yScale(d.value);
## })
## .attr("width", xScale.bandwidth())
## .attr("height", function(d) {
## return yScale(d.value);
## })
## .attr("fill", function(d) {
## return "rgb(0, 0, " + (d.value * 10) + ")";
## });
##
## //Etiquetas
## svg.selectAll("text")
## .data(dataset, key)
## .enter()
## .append("text")
## .text(function(d) {
## return d.value;
## })
## .attr("text-anchor", "middle")
## .attr("x", function(d, i) {
## return xScale(i) + xScale.bandwidth() / 2;
## })
## .attr("y", function(d) {
## return h - yScale(d.value) + 14;
## })
## .attr("font-family", "sans-serif")
## .attr("font-size", "11px")
## .attr("fill", "white");
##
##
##
##
## //Listener
## d3.selectAll("p")
## .on("click", function() {
##
##
## var paragraphID = d3.select(this).attr("id");
##
## //ifs
## if (paragraphID == "add") {
## //Add a data value
## var minValue = 2;
## var maxValue = 25 - minValue;
## var newNumber = Math.floor(Math.random() * maxValue) + minValue;
## var lastKeyValue = dataset[dataset.length - 1].key;
## dataset.push({
## key: lastKeyValue + 1,
## value: newNumber
## });
## } else {
## //eliminar
## dataset.shift();
## }
##
## //reescalamioento
## xScale.domain(d3.range(dataset.length));
## yScale.domain([0, d3.max(dataset, function(d) { return d.value; })]);
##
##
## var bars = svg.selectAll("rect")
## .data(dataset, key);
##
## // console.log('bars.enter()');
## // console.log(bars.enter());
##
## // console.log('bars.enter().append("rect")');
## // console.log(bars.enter().append("rect"));
##
## // console.log('bars.enter().append("rect").merge(bars)');
## // console.log(bars.enter().append("rect").merge(bars));
##
## //Enter…
## bars.enter()
## .append("rect")
## .attr("x", w)
## .attr("y", function(d) {
## return h - yScale(d.value);
## })
## .attr("width", xScale.bandwidth())
## .attr("height", function(d) {
## return yScale(d.value);
## })
## .attr("fill", function(d) {
## return "rgb(0, 0, " + (d.value * 10) + ")";
## })
## .merge(bars) //Update…
## .transition()
## .duration(500)
## .attr("x", function(d, i) {
## return xScale(i);
## })
## .attr("y", function(d) {
## return h - yScale(d.value);
## })
## .attr("width", xScale.bandwidth())
## .attr("height", function(d) {
## return yScale(d.value);
## });
##
## // console.log('bars.exit()');
## // console.log(bars.exit());
##
## // console.log('bars.exit().remove()');
## // console.log(bars.exit().remove());
##
## //Exit…
## bars.exit()
## .transition()
## .duration(500)
## .attr("x", -xScale.bandwidth())
## .remove();
##
##
##
## //Actualizar
##
## //Select…
## var labels = svg.selectAll("text")
## .data(dataset, key);
##
## //Exit…
## labels.exit()
## .transition()
## .duration(500)
## .attr("x", -xScale.bandwidth())
## .remove();
##
## //Enter…
## labels.enter()
## .append("text")
## .text(function(d) {
## return d.value;
## })
## .attr("text-anchor", "middle")
## .attr("x", w)
## .attr("y", function(d) {
## return h - yScale(d.value) + 14;
## })
## .attr("font-family", "sans-serif")
## .attr("font-size", "11px")
## .attr("fill", "white")
## .merge(labels) //Update…
## .transition()
## .duration(500)
## .attr("x", function(d, i) {
## return xScale(i) + xScale.bandwidth() / 2;
## });
##
## });
##
##
## </script>
## </body>
## </html>
## ```
Eventualmente nos interesa resaltar observaciones que tienen cierto valor o condición. Esta visualización esta diseñado para indicar como se puede seleccionar un grupo de observaciones ya sea para resaltar. UNa vez que estan ubicados los puntos es muy facil proceder con cualqueir acción.
## ```js
##
##
## <!DOCTYPE html>
## <html lang="en">
## <head>
## <meta charset="utf-8">
## <title>D3: Using radio buttons to set filter threshold value</title>
## <script src="https://d3js.org/d3.v7.min.js"></script>
## <style type="text/css">
##
## p {
## font-family: Helvetica, sans-serif;
## font-size: 12px;
## }
##
## input[type=radio] {
## margin-left: 40px;
## }
## .button {
## border: yes;
## color: red;
## padding: 15px 32px;
## text-align: center;
## text-decoration: none;
## display: inline-block;
## font-size: 16px;
## margin: 4px 2px;
## cursor: pointer;
## }
##
## </style>
## </head>
## <body>
## <p> Seleccione el umbral de las observaciones con los botones </p>
## <p>
## <input type="radio" name="filterPreset" value="0" checked="true"> 0
## <input type="radio" name="filterPreset" value="200"> 200
## <input type="radio" name="filterPreset" value="500"> 500
## <input type="radio" name="filterPreset" value="800"> 800
## </p>
##
## <p>
## <button type="button">Borrar Seleccion !</button>
## </p>
##
##
## <script type="text/javascript">
##
## //Ancho y alto
## var w = 600;
## var h = 300;
## var padding = 40;
##
## //Creamos observaciónes aleatorias
## var dataset = [];
## var numDataPoints = 200;
## var xRange = 1000;
## var yRange = 1000;
## for (var i = 0; i < numDataPoints; i++) {
## var newNumber1 = Math.floor(Math.random() * xRange);
## var newNumber2 = Math.floor(Math.random() * yRange);
## dataset.push([newNumber1, newNumber2]);
## }
##
## //Escalas
## var xScale = d3.scaleLinear()
## .domain([0, 1000])
## .range([padding, w - padding / 2]);
##
## var yScale = d3.scaleLinear()
## .domain([0, 1000])
## .range([h - padding, padding / 2]);
##
## // X axis
## var xAxis = d3.axisBottom()
## .scale(xScale)
## .ticks(5);
##
## // Y axis
## var yAxis = d3.axisLeft()
## .scale(yScale)
## .ticks(5);
##
## // SVG
## var svg = d3.select("body")
## .append("svg")
## .attr("width", w)
## .attr("height", h);
##
## //Dibujamos los puntos
## var allCircles = 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.5);
##
## // X axis
## svg.append("g")
## .attr("class", "axis")
## .attr("transform", "translate(0," + (h - padding) + ")")
## .call(xAxis);
##
## // Y axis
## svg.append("g")
## .attr("class", "axis")
## .attr("transform", "translate(" + padding + ",0)")
## .call(yAxis);
##
## //Botones radio
## d3.selectAll("input")
## .on("click", function() { //Listener
##
## var threshold = +d3.select(this).node().value;
##
## allCircles.attr("fill", "black")
## .filter(function(d) { return d[0] <= threshold;})
## .attr("fill", "red");
##
## });
##
##
##
##
##
## d3.selectAll("button")
## .on("click", function() { //Listener borrar
## var threshold = d3.select('input:checked').node().value
## console.log(threshold)
## var allCircles = svg.selectAll("circle");
## allCircles.attr("fill", "black")
## .filter(function(d) { return d[0] <= threshold;})
## .remove()
##
## });
##
## </script>
## </body>
## </html>
## ```
D3 es suficientemente potente como para crear simulaciones en tiempo real. en la siguiente simulación vamos a utilizar un modelo de física que permite modelar las fuerzas de atracción o rechazo de un conjunto de nodos. El modelo es interesante porque permite observar el efecto de listeners y manejadores para implementar funciones de arrastre (drag-n-drop)
## ```js
##
##
## <!DOCTYPE html>
## <html lang="en">
## <head>
## <meta charset="utf-8">
## <title>D3: Force layout</title>
## <script src="https://d3js.org/d3.v7.min.js"></script>
## <style type="text/css">
## /* NO CSS */
## </style>
## </head>
## <body>
## <script type="text/javascript">
##
## //aNCHO Y ALTO
## var w = 500;
## var h = 300;
##
## // data
## var dataset = {
## nodes: [
## { name: "A" },
## { name: "C" },
## { name: "C" },
## { name: "D" },
## { name: "E" },
## { name: "F" },
## { name: "G" },
## { name: "H" },
## { name: "I" },
## { name: "J" }
## ],
## edges: [
## { source: 0, target: 1 },
## { source: 0, target: 2 },
## { source: 0, target: 3 },
## { source: 0, target: 4 },
## { source: 1, target: 5 },
## { source: 2, target: 5 },
## { source: 2, target: 5 },
## { source: 3, target: 4 },
## { source: 5, target: 8 },
## { source: 5, target: 9 },
## { source: 6, target: 7 },
## { source: 7, target: 8 },
## { source: 8, target: 9 }
## ]
## };
##
## //INICIALIZO FORCE
## var force = d3.forceSimulation(dataset.nodes)
## .force("charge", d3.forceManyBody())
## .force("link", d3.forceLink(dataset.edges))
## .force("center", d3.forceCenter().x(w/2).y(h/2));
##
## var colors = d3.scaleOrdinal(d3.schemeCategory10);
##
## //SVG
## var svg = d3.select("body")
## .append("svg")
## .attr("width", w)
## .attr("height", h);
##
## //Lineas
## var edges = svg.selectAll("line")
## .data(dataset.edges)
## .enter()
## .append("line")
## .style("stroke", "#ccc")
## .style("stroke-width", 1);
##
## //Crear nodos
## var nodes = svg.selectAll("circle")
## .data(dataset.nodes)
## .enter()
## .append("circle")
## .attr("r", 10)
## .style("fill", function(d, i) {
## return colors(i);
## })
## .call(d3.drag() //listener
## .on("start", dragStarted)
## .on("drag", dragging)
## .on("end", dragEnded));
##
## // tooltip
## nodes.append("title")
## .text(function(d) {
## return d.name;
## });
##
## //Simulación de tiempo
## force.on("tick", function() {
##
## edges.attr("x1", function(d) { return d.source.x; })
## .attr("y1", function(d) { return d.source.y; })
## .attr("x2", function(d) { return d.target.x; })
## .attr("y2", function(d) { return d.target.y; });
##
## nodes.attr("cx", function(d) { return d.x; })
## .attr("cy", function(d) { return d.y; });
##
## });
##
## //los handlers
## function dragStarted(d) {
## if (!event.active) force.alphaTarget(0.3).restart();
## d.fx = d.x;
## d.fy = d.y;
## }
##
## function dragging(event, d ) {
## d.fx = event.x;
## d.fy = event.y;
## d.fixed = true;
## console.log(d.fx + " " +d.fy)
## }
##
## function dragEnded(event, d ) {
## if (!event.active) force.alphaTarget(0);
## d.fx = null;
## d.fy = null;
## }
##
## </script>
## </body>
## </html>
##
## ```