examples ejemplos d3js javascript responsive-design d3.js

javascript - ejemplos - ¿Cuál es la mejor manera de hacer que un diseño de visualización d3.js sea sensible?



d3js charts examples (11)

Supongamos que tengo un script de histograma que construye un gráfico de 960 500 svg. ¿Cómo hago que esto responda de manera tal que cambie el tamaño de los anchos y alturas de los gráficos que son dinámicos?

<script> var n = 10000, // number of trials m = 10, // number of random variables data = []; // Generate an Irwin-Hall distribution. for (var i = 0; i < n; i++) { for (var s = 0, j = 0; j < m; j++) { s += Math.random(); } data.push(s); } var histogram = d3.layout.histogram() (data); var width = 960, height = 500; var x = d3.scale.ordinal() .domain(histogram.map(function(d) { return d.x; })) .rangeRoundBands([0, width]); var y = d3.scale.linear() .domain([0, d3.max(histogram.map(function(d) { return d.y; }))]) .range([0, height]); var svg = d3.select("body").append("svg") .attr("width", width) .attr("height", height); svg.selectAll("rect") .data(histogram) .enter().append("rect") .attr("width", x.rangeBand()) .attr("x", function(d) { return x(d.x); }) .attr("y", function(d) { return height - y(d.y); }) .attr("height", function(d) { return y(d.y); }); svg.append("line") .attr("x1", 0) .attr("x2", width) .attr("y1", height) .attr("y2", height); </script>

El ejemplo completo del histograma es: https://gist.github.com/993912


Sin utilizar ViewBox

Este es un ejemplo de una solución que no se basa en el uso de un viewBox :

La clave está en actualizar el rango de las escalas que se utilizan para colocar datos.

Primero, calcula tu relación de aspecto original:

var ratio = width / height;

Luego, en cada cambio de tamaño, actualice el range de x e y :

function resize() { x.rangeRoundBands([0, window.innerWidth]); y.range([0, window.innerWidth / ratio]); svg.attr("height", window.innerHeight); }

Tenga en cuenta que la altura se basa en el ancho y la relación de aspecto, de modo que se mantengan sus proporciones originales.

Finalmente, "vuelva a dibujar" el gráfico: actualice cualquier atributo que dependa de cualquiera de las escalas x o y :

function redraw() { rects.attr("width", x.rangeBand()) .attr("x", function(d) { return x(d.x); }) .attr("y", function(d) { return y.range()[1] - y(d.y); }) .attr("height", function(d) { return y(d.y); }); }

Tenga en cuenta que al cambiar el tamaño de los rects puede usar el límite superior del range de y , en lugar de usar explícitamente la altura:

.attr("y", function(d) { return y.range()[1] - y(d.y); })

var n = 10000, // number of trials m = 10, // number of random variables data = []; // Generate an Irwin-Hall distribution. for (var i = 0; i < n; i++) { for (var s = 0, j = 0; j < m; j++) { s += Math.random(); } data.push(s); } var histogram = d3.layout.histogram() (data); var width = 960, height = 500; var ratio = width / height; var x = d3.scale.ordinal() .domain(histogram.map(function(d) { return d.x; })) var y = d3.scale.linear() .domain([0, d3.max(histogram, function(d) { return d.y; })]) var svg = d3.select("body").append("svg") .attr("width", "100%") .attr("height", height); var rects = svg.selectAll("rect").data(histogram); rects.enter().append("rect"); function redraw() { rects.attr("width", x.rangeBand()) .attr("x", function(d) { return x(d.x); }) // .attr("y", function(d) { return height - y(d.y); }) .attr("y", function(d) { return y.range()[1] - y(d.y); }) .attr("height", function(d) { return y(d.y); }); } function resize() { x.rangeRoundBands([0, window.innerWidth]); y.range([0, window.innerWidth / ratio]); svg.attr("height", window.innerHeight); } d3.select(window).on(''resize'', function() { resize(); redraw(); }) resize(); redraw();

<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>


Busque ''SVG responsivo''. Es bastante simple hacer que un SVG responda y no tiene que preocuparse más por los tamaños.

Así es como lo hice:

d3.select("div#chartId") .append("div") .classed("svg-container", true) //container class to make it responsive .append("svg") //responsive SVG needs these 2 attributes and no width and height attr .attr("preserveAspectRatio", "xMinYMin meet") .attr("viewBox", "0 0 600 400") //class to make it responsive .classed("svg-content-responsive", true);

El código CSS:

.svg-container { display: inline-block; position: relative; width: 100%; padding-bottom: 100%; /* aspect ratio */ vertical-align: top; overflow: hidden; } .svg-content-responsive { display: inline-block; position: absolute; top: 10px; left: 0; }

Más información / tutoriales:

http://demosthenes.info/blog/744/Make-SVG-Responsive

http://soqr.fr/testsvg/embed-svg-liquid-layout-responsive-web-design.php


En caso de que la gente siga visitando esta pregunta, esto es lo que funcionó para mí:

  • Encierre el iframe en un div y use css para agregar un relleno de, digamos, 40% a ese div (el porcentaje depende de la relación de aspecto que desee). A continuación, establezca el ancho y la altura del iframe en 100%.

  • En el documento html que contiene el gráfico que se cargará en el iframe, establezca el ancho al ancho del div al que se agrega el svg (o al ancho del cuerpo) y establezca la relación de aspecto altura a ancho *.

  • Escriba una función que vuelva a cargar el contenido del iframe al cambiar el tamaño de la ventana, para adaptar el tamaño de la tabla cuando las personas giren su teléfono.

Ejemplo aquí en mi sitio web: http://dirkmjk.nl/en/2016/05/embedding-d3js-charts-responsive-website

ACTUALIZACIÓN 30 dic 2016

El enfoque que describí anteriormente tiene algunos inconvenientes, especialmente porque no tiene en cuenta la altura de ningún título y subtítulos que no sean parte del svg creado por D3. Desde entonces he encontrado lo que creo que es un mejor enfoque:

  • Establezca el ancho del gráfico D3 al ancho del div al que se adjunta y use la relación de aspecto para establecer su altura en consecuencia;
  • Haga que la página incrustada envíe su altura y url a la página principal utilizando el mensaje Post de HTML5;
  • En la página principal, use la url para identificar el iframe correspondiente (útil si tiene más de un iframe en su página) y actualice su altura a la altura de la página incrustada.

Ejemplo aquí en mi sitio web: http://dirkmjk.nl/en/2016/12/embedding-d3js-charts-responsive-website-better-solution


En el caso de que esté utilizando un contenedor d3 como plottable.js , tenga en cuenta que la solución más sencilla podría ser agregar un detector de eventos y luego llamar a una función de redraw ( redraw a redraw en plottable.js). En el caso de plottable.js, esto funcionará de manera excelente (este enfoque está pobremente documentado):

window.addEventListener("resize", function() { table.redraw(); });


Evitaría soluciones de cambio de tamaño / marca como la plaga, ya que son ineficientes y pueden causar problemas en su aplicación (por ejemplo, una información sobre herramientas vuelve a calcular la posición en la que debería aparecer en el tamaño de la ventana, luego, un momento después, su gráfico también cambia de tamaño y la página vuelve a aparecer). diseños y ahora su información sobre herramientas está mal de nuevo).

Puede simular este comportamiento en algunos navegadores antiguos que no lo admiten correctamente, como IE11 también, utilizando un elemento <canvas> que mantiene su aspecto.

Dado 960x540 que es un aspecto de 16: 9:

<div style="position: relative"> <canvas width="16" height="9" style="width: 100%"></canvas> <svg viewBox="0 0 960 540" preserveAspectRatio="xMidYMid meet" style="position: absolute; top: 0; right: 0; bottom: 0; left: 0; -webkit-tap-highlight-color: transparent;"> </svg> </div>


Hay otra manera de hacer esto que no requiere volver a dibujar el gráfico, e implica modificar los viewBox y preserveAspectRatio en el elemento <svg> :

<svg id="chart" width="960" height="500" viewBox="0 0 960 500" preserveAspectRatio="xMidYMid meet"> </svg>

Actualización 11/24/15 : la mayoría de los navegadores modernos pueden inferir la relación de aspecto de los elementos SVG desde el viewBox , por lo que es posible que no necesite mantener actualizado el tamaño del gráfico. Si necesita admitir navegadores más antiguos, puede cambiar el tamaño de su elemento cuando la ventana se redimensiona así:

var aspect = width / height, chart = d3.select(''#chart''); d3.select(window) .on("resize", function() { var targetWidth = chart.node().getBoundingClientRect().width; chart.attr("width", targetWidth); chart.attr("height", targetWidth / aspect); });

Y los contenidos svg serán escalados automáticamente. Puede ver un ejemplo práctico de esto (con algunas modificaciones) here : simplemente cambie el tamaño de la ventana o del panel inferior derecho para ver cómo reacciona.


He codificado una pequeña cosa para resolver esto.

El patrón de solución general es este:

  1. Desglose el guión en funciones de cálculo y dibujo.
  2. Asegúrese de que la función de dibujo se dibuje dinámicamente y se guíe por las variables de ancho y alto de visualización (la mejor manera de hacerlo es usar la api d3.scale)
  3. Enlazar / encadenar el dibujo a un elemento de referencia en el marcado. (Utilicé jquery para esto, así que lo importé).
  4. Recuerda quitarlo si ya está dibujado. Obtenga las dimensiones del elemento referenciado utilizando jquery.
  5. Enlazar / encadenar la función de dibujar a la función de cambio de tamaño de la ventana. Introduzca una rebote (timeout) en esta cadena para asegurarse de que solo se vuelva a dibujar después de un timeout.

También agregué el script d3.js minified para la velocidad. La esencia está aquí: https://gist.github.com/2414111

Código de referencia de jQuery:

$(reference).empty() var width = $(reference).width();

Código de rebote:

var debounce = function(fn, timeout) { var timeoutID = -1; return function() { if (timeoutID > -1) { window.clearTimeout(timeoutID); } timeoutID = window.setTimeout(fn, timeout); } }; var debounced_draw = debounce(function() { draw_histogram(div_name, pos_data, neg_data); }, 125); $(window).resize(debounced_draw);

¡Disfrutar!


La respuesta de Shawn Allen fue genial. Pero es posible que no quiera hacer esto cada vez. Si lo aloja en vida.io , obtiene respuesta automática para su visualización svg.

Puede obtener iframe con este simple código de inserción:

<div id="vida-embed"> <iframe src="http://embed.vida.io/documents/9Pst6wmB83BgRZXgx" width="auto" height="525" seamless frameBorder="0" scrolling="no"></iframe> </div> #vida-embed iframe { position: absolute; top:0; left: 0; width: 100%; height: 100%; }

http://jsfiddle.net/dnprock/npxp3v9d/1/

Divulgación: construyo esta característica en vida.io.


Si está utilizando d3.js a través de c3.js la solución al problema de la capacidad de respuesta es bastante sencilla:

var chart = c3.generate({bindTo:"#chart",...}); chart.resize($("#chart").width(),$("#chart").height());

donde el HTML generado se ve como:

<div id="chart"> <svg>...</svg> </div>


También puede usar bootstrap 3 para adaptar el tamaño de una visualización. Por ejemplo, podemos configurar el código HTML como:

<div class="container> <div class="row"> <div class=''col-sm-6 col-md-4'' id="month-view" style="height:345px;"> <div id ="responsivetext">Something to write</div> </div> </div> </div>

He configurado una altura fija debido a mis necesidades, pero también puede dejar el tamaño automático. El "col-sm-6 col-md-4" hace que la div sea sensible para diferentes dispositivos. Puede obtener más información en http://getbootstrap.com/css/#grid-example-basic

Podemos acceder al gráfico con la ayuda de la vista de mes de id.

No voy a entrar en muchos detalles sobre el código d3, solo ingresaré la parte que se necesita para adaptarse a diferentes tamaños de pantalla.

var width = document.getElementById(''month-view'').offsetWidth; var height = document.getElementById(''month-view'').offsetHeight - document.getElementById(''responsivetext2'').offsetHeight;

El ancho se establece al obtener el ancho del div con la vista de mes id.

La altura en mi caso no debe incluir toda el área. También tengo un texto encima de la barra, así que necesito calcular esa área también. Es por eso que identifiqué el área del texto con el identificador responsivetext. Para calcular la altura permitida de la barra, resté la altura del texto de la altura de la div.

Esto le permite tener una barra que adoptará todos los diferentes tamaños de pantalla / div. Puede que no sea la mejor manera de hacerlo, pero seguramente funciona para las necesidades de mi proyecto.


Uno de los principios básicos de la unión de datos D3 es que es idempotente. En otras palabras, si evalúa repetidamente una unión de datos con los mismos datos, la salida representada es la misma. Por lo tanto, siempre que represente su gráfico correctamente, tenga cuidado con sus selecciones de ingreso, actualización y salida; todo lo que tiene que hacer cuando cambia el tamaño, es volver a representar el gráfico en su totalidad.

Hay un par de otras cosas que debes hacer, una de ellas es quitar el rebote del controlador de cambio de tamaño de la ventana para estrangularlo. Además, en lugar de anchos / alturas de codificación rígida, esto debe lograrse midiendo el elemento contenedor.

Como alternativa, aquí se presenta su gráfico utilizando d3fc , que es un conjunto de componentes D3 que manejan correctamente las uniones de datos. También tiene un gráfico cartesiano que mide el elemento que contiene, lo que facilita la creación de gráficos "sensibles":

// create some test data var data = d3.range(50).map(function(d) { return { x: d / 4, y: Math.sin(d / 4), z: Math.cos(d / 4) * 0.7 }; }); var yExtent = fc.extentLinear() .accessors([ function(d) { return d.y; }, function(d) { return d.z; } ]) .pad([0.4, 0.4]) .padUnit(''domain''); var xExtent = fc.extentLinear() .accessors([function(d) { return d.x; }]); // create a chart var chart = fc.chartSvgCartesian( d3.scaleLinear(), d3.scaleLinear()) .yDomain(yExtent(data)) .yLabel(''Sine / Cosine'') .yOrient(''left'') .xDomain(xExtent(data)) .xLabel(''Value'') .chartLabel(''Sine/Cosine Line/Area Chart''); // create a pair of series and some gridlines var sinLine = fc.seriesSvgLine() .crossValue(function(d) { return d.x; }) .mainValue(function(d) { return d.y; }) .decorate(function(selection) { selection.enter() .style(''stroke'', ''purple''); }); var cosLine = fc.seriesSvgArea() .crossValue(function(d) { return d.x; }) .mainValue(function(d) { return d.z; }) .decorate(function(selection) { selection.enter() .style(''fill'', ''lightgreen'') .style(''fill-opacity'', 0.5); }); var gridlines = fc.annotationSvgGridline(); // combine using a multi-series var multi = fc.seriesSvgMulti() .series([gridlines, sinLine, cosLine]); chart.plotArea(multi); // render d3.select(''#simple-chart'') .datum(data) .call(chart);

Puedes verlo en acción en este codepen:

https://codepen.io/ColinEberhardt/pen/dOBvOy

donde puede cambiar el tamaño de la ventana y verificar que el gráfico se haya vuelto a representar correctamente.

Tenga en cuenta, como una divulgación completa, soy uno de los mantenedores de d3fc.