1

I am using LightningChartJS and I need to maintain an aspect ratio between the X and Y axis.

Currently, LCJS automatically scales the chart based on the size of the container. What I would like is to "fix" this if possible. So that the ticks/points take up the same amount of space (in a pixel sense) independent of the total width/height of the chart.

Or if this isn't possible, is there a way to guarantee that X units of height will always equal Y units of width?

Christian
  • 1,676
  • 13
  • 19

1 Answers1

0

As of yet there is no dedicated feature for this, so it requires modifying the custom chart behaviour. Since there are lately an increasing amount of questions regarding this, we'll be planning out a dedicated solution in the near future (2021).

First off, commonly this need for "static aspect ratio" comes from the desire to ensure that the data visualization doesn't stretch in any scenario. This is affected by two things:

  1. Size of chart area in pixels.
  2. Axis intervals.

Like you mentioned, by default LCJS scales the chart based on the size of the container. To ensure that the chart width and height ratio always stays the same it is best to create the HTML DIV element that contains the chart by yourself and use CSS.

Below you'll find an example of this based on some quick googling "how to keep aspect ratio with CSS". See, how the chart width and height keep their aspect ratio when window is resized (Run code snippet + full page).

Note that the aspect ratio used in CSS style doesn't have to relate to the actual data visualization. The important part is that the ratio between width and height is set to a constant, so that the chart doesn't stretch.

const {
  lightningChart
} = lcjs

const container = document.getElementById('chart')
const chart = lightningChart().ChartXY({ container })
/* https://stackoverflow.com/questions/20590239/maintain-aspect-ratio-of-div-but-fill-screen-width-and-height-in-css/36295495#20593342 */
#chart {
  width: 100vw; 
  height: 56.25vw; /* height:width ratio = 9/16 = .5625  */
  background: pink;
  max-height: 100vh;
  max-width: 177.78vh; /* 16/9 = 1.778 */
  margin: auto;
  position: absolute;
  top:0;bottom:0; /* vertical center */
  left:0;right:0; /* horizontal center */
}
<script src="https://unpkg.com/@arction/xydata@1.4.0/dist/xydata.iife.js"></script>
<script src="https://unpkg.com/@arction/lcjs@3.0.0/dist/lcjs.iife.js"></script>
<div id='chart'></div>

Afterwards, there is the issue of ensuring that the ratio between axis intervals doesn't change from the desired ratio. There are many LCJS features which can affect the axis intervals:

  • Automatic scrolling. I'll assume that for an application with static aspect ratio, automatic scrolling is not an absolute necessity, so I would advise to configure Axis intervals manually.
chart.getDefaultAxisX().setScrollStrategy(undefined).setInterval(0, 100)
chart.getDefaultAxisY().setScrollStrategy(undefined).setInterval(0, 100)

If user interactions are not required, instead of following points, all interactions can be disabled (setMouseInteractions method).

  • Rectangle zooming. The event which by default activates when pressing left mouse button down and dragging above XY chart, this is not logical for static aspect ratio chart, so I would advise to disable it.
chart.setMouseInteractionRectangleZoom(false)
  • Axis interactions. Other than panning, all single Axis oriented user interactions are not logical for static aspect ratio chart, so I would advise to disable them.
chart.getDefaultAxisX()
  .setAxisInteractionZoomByDragging(false) 
  .setAxisInteractionZoomByWheeling(false) 
  .setNibInteractionScaleByDragging(false)
  .setNibInteractionScaleByWheeling(false)

The remaining default user interactions are panning and zooming with mouse, which should not affect the ratio between axis intervals.

Here is a code snippet that:

  1. Uses CSS to set static ratio between chart container width and height.
  2. Disables features that would change the X/Y Axis interval ratio.
  3. Generates some random data and fits it into the view. In this case the actual data aspect ratio comes from the boundaries of generated random data. Key here is that this ratio should afterwards stay the same for the entire lifecycle of the application.

const {
  lightningChart
} = lcjs

const {
  createProgressiveTraceGenerator,
} = xydata

const container = document.getElementById('chart')
const chart = lightningChart().ChartXY({ container })
const axisX = chart.getDefaultAxisX()
const axisY = chart.getDefaultAxisY()

// Configure chart and axes for static aspect ratio application.
chart
  .setMouseInteractionRectangleZoom(false)

axisX
  .setScrollStrategy(undefined)
  .setAxisInteractionZoomByDragging(false) 
  .setAxisInteractionZoomByWheeling(false) 
  .setNibInteractionScaleByDragging(false)
  .setNibInteractionScaleByWheeling(false)

axisY
  .setScrollStrategy(undefined)
  .setAxisInteractionZoomByDragging(false) 
  .setAxisInteractionZoomByWheeling(false) 
  .setNibInteractionScaleByDragging(false)
  .setNibInteractionScaleByWheeling(false)

// Add some data series.
const areaRangeSeries = chart.addAreaRangeSeries()
Promise.all(new Array(2).fill(0).map(_ => 
  createProgressiveTraceGenerator()
    .setNumberOfPoints(100)
    .generate()
    .toPromise()
))
  .then(traceData2 => {
    return traceData2[0].map((_, i) => ({
      position: i,
      low: Math.min(traceData2[0][i].y, traceData2[1][i].y),
      high: Math.max(traceData2[0][i].y, traceData2[1][i].y),
    }))
  })
  .then(areaRangeData => {
    areaRangeSeries.add(areaRangeData)
    axisX.fit(false)
    axisY.fit(false)
  })

// Debug active aspect ratio.
const labelAspectRatio = document.getElementById('debug-aspect-ratio')
const updateDebug = () => {
  const activeAspectRatio = (axisY.getInterval().end - axisY.getInterval().start) / (axisX.getInterval().end - axisX.getInterval().start)
  labelAspectRatio.innerHTML = `Axis aspect ratio (height/width): ${activeAspectRatio.toFixed(4)}`
}
setInterval(updateDebug, 100)
/* https://stackoverflow.com/questions/20590239/maintain-aspect-ratio-of-div-but-fill-screen-width-and-height-in-css/36295495#20593342 */
#chart {
  width: 100vw; 
  height: 56.25vw; /* height:width ratio = 9/16 = .5625  */
  background: pink;
  max-height: 100vh;
  max-width: 177.78vh; /* 16/9 = 1.778 */
  margin: auto;
  position: absolute;
  top:0;bottom:0; /* vertical center */
  left:0;right:0; /* horizontal center */
}
#debug-aspect-ratio {
  color: black;
  z-index: 100;
}
<script src="https://unpkg.com/@arction/xydata@1.4.0/dist/xydata.iife.js"></script>
<script src="https://unpkg.com/@arction/lcjs@3.0.0/dist/lcjs.iife.js"></script>
<span id='debug-aspect-ratio'></span>
<div id='chart'></div>

At this time, this might sound overwhelmingly complex, but rest assured we will be showing extra love to the scientific LCJS usage cases in the near future.

If you have more issues regarding aspect ratios, please comment or edit the question. It could be important information for the development of a dedicated feature.

Niilo Keinänen
  • 2,187
  • 1
  • 7
  • 12
  • Since the last update, we've published a handful of examples which also require static aspect ratio - mainly charts that are laid over pictures - these might be very good references for this kind of requirements. Here are some links: https://www.arction.com/lightningchart-js-interactive-examples/examples/lcjs-example-1110-geoChartUsaTemperature.html https://www.arction.com/lightningchart-js-interactive-examples/examples/lcjs-example-0025-officeDataVisualizationLayer.html https://www.arction.com/lightningchart-js-interactive-examples/examples/lcjs-example-0019-flowCytometryChart.html – Niilo Keinänen Dec 13 '21 at 14:47