Pregunta

Suponga que tengo un script de histograma que construye un gráfico 960 500 SVG. ¿Cómo hago que esto responda para cambiar el tamaño de los anchos gráficos y las alturas son dinámicas?

<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> 

Ejemplo completo de histograma GIST es:https://gist.github.com/993912

¿Fue útil?

Solución

Hay otra forma de hacer esto que no requiere volver a dibujar el gráfico, e implica modificar el viewbox y preservar la relación de aspecto atributos en el <svg> elemento:

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

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

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 el contenido de SVG se escalará automáticamente. Puede ver un ejemplo de trabajo de esto (con algunas modificaciones) aquí: Simplemente cambie el tamaño de la ventana o el panel inferior derecho para ver cómo reacciona.

Otros consejos

Busque 'SVG receptivo' Es bastante simple hacer que SVG responda y ya no tiene que preocuparse 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-wesign.php

He codificado una pequeña esencia para resolver esto.

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

  1. Rompa el script en funciones de cálculo y dibujo.
  2. Asegúrese de que la función de dibujo se dibuja dinámicamente y está impulsada de las variables de ancho y altura de visualización (la mejor manera de hacerlo es usar la API D3.Scale)
  3. Atar/cadena el dibujo a un elemento de referencia en el marcado. (Usé jQuery para esto, así que lo importé).
  4. Recuerde eliminarlo si ya está dibujado. Obtenga las dimensiones del elemento referenciado usando jQuery.
  5. Bind/Chain La función de dibujo a la función de cambio de tamaño de la ventana. Introducir un debaldad (tiempo de espera) a esta cadena para asegurarse de que solo rediseñemos después de un tiempo de espera.

También agregué el script D3.JS minificado para la velocidad. La esencia está aquí: https://gist.github.com/2414111

Código de retroceso de referencia jQuery:

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

Código de desbloqueo:

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!

Sin usar ViewBox

Aquí hay un ejemplo de una solución que no confía en usar un viewBox:

La clave es actualizar el rango del escamas que se utilizan para colocar datos.

Primero, calcule su relación de aspecto original:

var ratio = width / height;

Luego, en cada cambio de tamaño, actualice el range de x y 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, "redibuje" el gráfico: actualice cualquier atributo que dependa de cualquiera de los x o y escamas:

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 volver a tamaño el rects puedes usar la parte superior de la 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>

Si está usando 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 se ve el HTML generado:

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

La respuesta de Shawn Allen fue genial. Pero es posible que no desee hacer esto cada vez. Si lo alojas Vida.io, obtienes una respuesta automática para tu visualización SVG.

Puede obtener un Iframe receptivo con este simple código de incrustació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 función en Vida.io.

En el caso de que esté usando un envoltura d3 me gusta Plottable.js, tenga en cuenta que la solución más fácil podría ser Agregar un oyente de eventos Y luego llamando a un volver a dibujar función (redraw en Plottable.js). En el caso de Plottable.js, esto funcionará de manera excelente (este enfoque está mal documentado):

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

En caso de que la gente todavía esté visitando esta pregunta, esto es lo que funcionó para mí:

  • Adjunte el iframe en un DIV y use CSS para agregar un relleno de, por ejemplo, 40% a ese DIV (el porcentaje dependiendo de la relación de aspecto que desee). Luego establezca tanto el ancho como la altura del iframe en el 100%.

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

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

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

Actualización 30 de diciembre de 2016

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

  • Establezca el ancho de la tabla D3 en el ancho del div 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 postmessaje 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

Uno de los principios básicos del unión de datos D3 es que es idempotente. En otras palabras, si evalúa repetidamente un unión de datos con los mismos datos, la salida renderizada es la misma. Por lo tanto, siempre y cuando tenga su cuadro correctamente, cuidando con sus selecciones de entrada, actualización y salida, todo lo que tiene que hacer cuando cambia el tamaño es volver a renderizar la tabla en su totalidad.

Hay un par de otras cosas que debes hacer, una es desbloquear el manejador de cambio de tamaño de la ventana para acelerarlo. Además, en lugar de anchos / alturas de codificación dura, esto debe lograrse midiendo el elemento que contiene.

Como alternativa, aquí está su cuadro reproducido usando D3FC, que es un conjunto de componentes D3 que manejan correctamente los datos de datos. También tiene una tabla cartesiana que mide que contenga elementos que facilita la creación de gráficos 'receptivos':

// 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 vuelva a renderizar correctamente.

Tenga en cuenta que, como divulgación completa, soy uno de los mantenedores de D3FC.

Evitaría las soluciones de cambio de tamaño/tick como la peste, 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 que debe aparecer en el cambio de tamaño de la ventana, luego un momento después su gráfico también se cambia de tamaño y la página diseños y ahora su información sobre herramientas vuelve a estar mal).

Puede simular este comportamiento en algunos navegadores más antiguos que no lo admiten adecuadamente como IE11 también usando un <canvas> elemento 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>

También puedes usar bootstrap 3 para adaptar el tamaño de una visualización. Por ejemplo, podemos configurar el Html código 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 puedes dejar el tamaño automático. El "Col-SM-6 COL-MD-4" hace que el DIV responda a diferentes dispositivos. Puedes aprender más en http://getbootstrap.com/css/#grid-example-basic

Podemos acceder al gráfico con la ayuda de la identificación visión del mes.

No 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 obteniendo el ancho del DIV con la Vista de Mes de ID.

La altura en mi caso no debe incluir toda el área. También tengo algún texto por encima de la barra, por lo que también necesito calcular esa área. Es por eso que identifiqué el área del texto con el ID ResponseIbtext. Para calcular la altura permitida de la barra, resté la altura del texto desde la altura del Div.

Esto le permite tener una barra que adopte 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.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top