简体   繁体   中英

D3 calculate x position from dates

I have a D3 plot where I plot time ( hh:mm ) on x against some values on y axis.

For the x scale I use this logic

    const currentData : string[] = ['06:00', '12:00', '18:00'];

    this.x = d3Scale.scalePoint(
      [0, this.width - this.margin.right - this.margin.left])
      .domain(
        currentData.map(
          (d: PlotData) => d.time))
      .round(true);
   

That plots all my data nicely. Now I would like to use this.x to return the value of a time point that is currently not in my original data. But when I run it with

this.x('14:00')

it returns NAN , which seems is because the input is not in the currentData array and to work only on values from the array.

Do I need to interpolate this value myself or is there a D3 function to take this.x and figure the this.x('14:00') internally?

Thanks, EL

It might be easier to instead use scaleTime

const currentData : string[] = ['06:00', '12:00', '18:00'];

this.x = d3.scaleTime()
  .domain((d: PlotData) => d.time))
  .range([0, this.width - this.margin.right - this.margin.left]);

this.x(new DateTime('xyz:abc'))

The other answer suggests the right approach, to use d3-scaleTime. I'd like to dig into why you see your problem, and then provide details to the solution with d3-scaleTime.

Ordinal Scales

d3.scalePoint is an ordinal scale. The domain represents a discrete and discontinuous set of values. The mapping of the domain to the range is based on the order in which domain values are specified. The first value in the domain is mapped to the first value in the range (with d3.scalePoint) regardless of its value as compared to other values in the domain.

As a result, only values that exist in the domain can be mapped to the range: there is no semantic/quantitative relationship between values in the domain that allows interpolation of in between values. Because you are using quantitative rather than qualitative or ordinal data, you want to use a scale that treats the domain as continuous.

There may be cases where you'd want to use an d3.scalePoint/Band scale with time based data, but in those cases you'd need to be able to construct a complete domain of all values that need to be plotted.

That your scale appears to plot your data as continuous at first is coincidental, if your middle domain value was '07:00', 'June' or 'yesterday', it would appear in the middle of your range. That you chose '12:00' hides the fact that the scale isn't positioning this based on its value but only on its index.

Continuous Scales

d3.scaleTime is one of d3's continuous scales

Continuous scales map a continuous, quantitative input domain to a continuous output range. ( docs )

Domains and Ranges

For d3's continuous scales, domain and range must contain the same number of elements - if there are more than two elements in each, the domain and range are split into segments:

 .domain([a,b,c]) // where a,b,c are quantitative
 .range([1,2,3])

Values between a and b will be interpolated between 1 and 2, likewise, values between b and c will be interpolated between 2 and 3. If domain and range have a different number of elements, D3 will skip excess values of the one that has more.

It appears in your case that just two domain values will be sufficient, the minimum and maximum values you want to pass to the scale.

Using Dates

d3.scaleTime's domain needs to be set with date objects not a string such as '12:00', also, when passing a value to the scale ( scale(x) ) we need to pass a date object. We can create a basic d3 date parser with:

 d3.parseTime("%H:%M")

Which gives us:

 const currentData = ['06:00', '12:00', '18:00']; const parse = d3.timeParse("%H:%M") const x = d3.scaleTime().range([0,100]).domain(d3.extent(currentData, (time) => parse(time))) console.log("15:00 scales to: ", x(parse('15:00'))) console.log("06:00 scales to: ", x(parse('06:00')))
 <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>

This should map your domain as before, but also allows you to also plot values that aren't in your initial dataset and have them mapped properly to the range based on their value.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM