Skip to content

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Chart v3.0.0: Time Cartesian Axis - w/o Date Adapters ??? #8771

Closed
zibous opened this issue Apr 2, 2021 · 28 comments
Closed

Chart v3.0.0: Time Cartesian Axis - w/o Date Adapters ??? #8771

zibous opened this issue Apr 2, 2021 · 28 comments

Comments

@zibous
Copy link

zibous commented Apr 2, 2021

Feature Proposal

I am looking for a way to use Time Cartesian Axis without a date adapter, because I have not found a way in the Home Assistant framework how I can use locale files for the adapter. Also I have my concerns, just to display the date / time in the user language, to use the date libs (moment, luxon, date-fn) for it.

I have different data sources that don't always have data for every time interval. It is possible to create a timeseries for Category Cartesian Axis for all data, but then my performance is poor, since many data always have 0 values.

Bildschirmfoto 2021-04-02 um 11 02 33

Most likely I could use the chartjs-adapter-date-fns, but I can display the date and time in the user language.

What is my option?

The dataset contains the following data:

X-Axis: Date
Y-Axis: Value

[
    {
        "x": 1617253200000,
        "y": 0.117,
    },
    {
        "x": 1617256800000,
        "y": 0.249,
    },
    {
        "x": 1617260400000,
        "y": 2.068,
    },
    ....

Feature Use Case

Time axis Ticks label (x) formatted and Tooltips date / time in the user language.

Possible Implementation

???

@kurkle
Copy link
Member

kurkle commented Apr 2, 2021

I don't really understand what the issue is.

  1. Can you detect the locale of the user?
  2. Can you format a timestamp to that detected locale in any way?
  3. Or would you need to provide pre-formatted values, in which case where do you get the locale from?

@zibous
Copy link
Author

zibous commented Apr 2, 2021

  • Can you detect the locale of the user?

Yes

  • Can you format a timestamp to that detected locale in any way?

Yes, but i do not know how i can use the formatted timestamp with the options

  • Or would you need to provide pre-formatted values, in which case where do you get the locale from?

??

This is my simple way to format the timestamp

      /**
         * get date format
         * @param {string} date
         * @param {string} format
         * @returns
         */
        function formatdate(date, format = "Y-m-d") {
            const groupFormats = {
                minute: "Y-m-d H:s",
                hour: "Y-m-d H:00",
                day: "Y-m-d 00:00",
                month: "Y-m-01 00:00",
                year: "Y-12-31 00:00"
            }
            format = groupFormats[format] || format || "Y-m-d"
            date = new Date(date)
            const hour24 = date.getHours()
            const hour12 = date.getHours() % 12 || 12
            const numDay = date.getDay()
            const numMonth = date.getMonth()
            const parts = {
                Y: date.getFullYear().toString(),
                y: ("00" + (date.getYear() - 100)).toString().slice(-2),
                m: ("0" + (numMonth + 1)).toString().slice(-2),
                n: (numMonth + 1).toString(),
                d: ("0" + date.getDate()).toString().slice(-2),
                j: date.getDate().toString(),
                H: ("0" + hour24).toString().slice(-2),
                h: ("0" + hour12).toString().slice(-2),
                G: hour24.toString(),
                g: hour12.toString(),
                a: hour24 >= 12 && hour24 < 24 ? "pm" : "am",
                A: hour24 >= 12 && hour24 < 24 ? "PM" : "AM",
                i: ("0" + date.getMinutes()).toString().slice(-2),
                s: ("0" + date.getSeconds()).toString().slice(-2),
                w: numDay,
                N: numDay > 0 ? numDay : 7,
                D: localeNames.days_short[numDay],
                l: localeNames.days_long[numDay],
                M: localeNames.month_short[numMonth],
                F: localeNames.month_long[numMonth]
            }
            const modifiers = Object.keys(parts).join("")
            const reDate = new RegExp("[" + modifiers + "]", "g")
            const dt = format.replace(reDate, ($0) => parts[$0])
            return dt
        }

@kurkle
Copy link
Member

kurkle commented Apr 2, 2021

So you are constructing the localeNames based on locale? You could just create a custom adapter.
Or you could use linear scale (time is not always linear though): https://codepen.io/kurkle/pen/abpJaqj

@zibous
Copy link
Author

zibous commented Apr 2, 2021

So you are constructing the localeNames based on locale?

the performance of Intl.DateTimeFormat is not very good, I load the translations when the locale is changed in order to use them later for the DateFormat.

/**
 * get the localized day and month names
 * @param {str} locale
 * @returns list
 */
function getLocaleNamesCached(localeName = "at-DE") {
    const datetext = {
        days_short: [...Array(7).keys()].map((day) =>
            new Intl.DateTimeFormat(localeName, { weekday: "short" }).format(new Date(Date.UTC(2021, 5, day)))
        ),
        days_long: [...Array(7).keys()].map((day) =>
            new Intl.DateTimeFormat(localeName, { weekday: "long" }).format(new Date(Date.UTC(2021, 5, day)))
        ),
        month_short: [...Array(12).keys()].map((month) =>
            new Intl.DateTimeFormat(localeName, { month: "short" }).format(new Date(Date.UTC(2021, month, 1)))
        ),
        month_long: [...Array(12).keys()].map((month) =>
            new Intl.DateTimeFormat(localeName, { month: "long" }).format(new Date(Date.UTC(2021, month, 1)))
        )
    }
    return datetext
}
const localeNames = getLocaleNamesCached(userLocale)

You could just create a custom adapter.

Yes, an alternative would be, but I don't (yet) understand the settings / methods of the date adapter.

Or you could use linear scale (time is not always linear though)

I like it 👍, would be a simple implementation, but don't know the restrictions yet? But would be the preferred method for me.

Here is an example of how I prepare the data:

[
    {
        "x": 1617253200000,
        "y": 0.117,
        "localedate": "Fr, 01 Apr 21 07:07"
    },
    {
        "x": 1617256800000,
        "y": 0.249,
        "localedate": "Fr, 01 Apr 21 08:21"
    },
    {
        "x": 1617260400000,
        "y": 2.068,
        "localedate": "Fr, 01 Apr 21 09:35"
    },
    {
        "x": 1617264000000,
        "y": 2.685,
        "localedate": "Fr, 01 Apr 21 10:49"
    },
    {
        "x": 1617267600000,
        "y": 2.666,
         "localedate": "Fr, 01 Apr 21 11:04"
    },
    {
        "x": 1617271200000,
        "y": 2.565,
        "localedate": "Fr, 01 Apr 21 12:18"
    },
    {
        "x": 1617274800000,
        "y": 2.567,
        "localedate": "Fr, 01 Apr 21 13:37"
    },
    {
        "x": 1617278400000,
        "y": 2.552,
        "localedate": "Fr, 01 Apr 21 14:51"
    },
    {
        "x": 1617282000000,
        "y": 1.776,
        "localedate": "Fr, 01 Apr 21 15:06"
    },
    {
        "x": 1617285600000,
        "y": 1.669,
        "localedate": "Fr, 01 Apr 21 16:20"
    },
    {
        "x": 1617289200000,
        "y": 1.251,
        "localedate": "Fr, 01 Apr 21 17:34"
    },
    {
        "x": 1617292800000,
        "y": 0.3,
        "localedate": "Fr, 01 Apr 21 18:49"
    },
    {
        "x": 1617296400000,
        "y": 0.069,
        "localedate": "Fr, 01 Apr 21 19:58"
    },
    {
        "x": 1617339600000,
        "y": 0.16,       
        "localedate": "Sa, 02 Apr 21 07:57"
    },
    {
        "x": 1617343200000,
        "y": 0.325,
        "localedate": "Sa, 02 Apr 21 08:16"
    },
    {
        "x": 1617346800000,
        "y": 0.544,
        "localedate": "Sa, 02 Apr 21 09:30"
    },
    {
        "x": 1617350400000,
        "y": 2.59,
        "localedate": "Sa, 02 Apr 21 10:44"
    },
    {
        "x": 1617354000000,
        "y": 2.627,   
        "localedate": "Sa, 02 Apr 21 11:03"
    },
    {
        "x": 1617357600000,
        "y": 2.647,        
        "localedate": "Sa, 02 Apr 21 12:15"
    }
]

@etimberg
Copy link
Member

etimberg commented Apr 2, 2021

Is the slow performance of Intl.DateTimeFormat the construction of the object or the actual format call? If you cached the formatter, would that make a difference?

@zibous
Copy link
Author

zibous commented Apr 2, 2021

@etimberg

Is the slow performance of Intl.DateTimeFormat the construction of the object or the actual format call? If you cached the formatter, would that make a difference?

Negativ cached formatter is also slow.
It is more efficient to read the texts for the translation only when the locale is changed and to read them later from the text map.

@zibous
Copy link
Author

zibous commented Apr 2, 2021

@kurkle

Or you could use linear scale (time is not always linear though)

Bad news, I can use your example statically and it works. Unfortunately, if I create the options with Javascript, I cannot add a callBack function:

             options.scales = _options.scales || {}
             options.scales.x = _options.scales.x || {}
             options.scales.x.type = "linear"
             options.scales.x.ticks = _options.scales.x.ticks || {}
             options.scales.x.ticks = {
                callback: function(value, index, values) {
                    return new Date(value).toLocaleString()
                }
            }
            options.scales.y = _options.scales.y || {}
            options.scales.y.type = "linear"

@kurkle
Copy link
Member

kurkle commented Apr 3, 2021

That is not a limitation of javascript or Chart.js. Why can't you add a callback?

@zibous
Copy link
Author

zibous commented Apr 3, 2021

@kurkle
Thanks.

Yes you are right, it was my mistake.

It works now, but I don't yet know how to access the dataset or how to get the date format from the options.

In the scales.x.ticks.callback(...) do I only get the data of the x-axis?

/**
 * format the x-axis date/time label
 * @param {*} value
 * @param {*} index
 * @param {*} values
 * @returns
 */
const xAxisFormat = (value, index, values) => {
    // TODO: how to get this from the options ???
    const formatPattern = "longdate" // just for testing
    if (Number.isInteger(values[index].value)) {
        return formatdate(+values[index].value, formatPattern)
    }
    return values[index].value
}


options.scales = _options.scales || {}
options.scales.x = _options.scales.x || {}
options.scales.x.type = "time"
options.scales.x.time = {
   unit: this.card_config.datascales.unit,
   displayFormats: {},                
}
options.scales.x.ticks = { callback: xAxisFormat }

Works only for options.scales.x.type = "time" not for options.scales.x.type = "linear"

@kurkle
Copy link
Member

kurkle commented Apr 3, 2021

Take a look at the default formatters (assigned as callback):
https://github.com/chartjs/Chart.js/blob/master/src/core/core.ticks.js

this is the scale, so you can access the chart and options (or scale options) through that.

@zibous
Copy link
Author

zibous commented Apr 3, 2021

@kurkle

Negativ this is in the callback xAxisFormat not valid.

const xAxisFormat = (value, index, values) => {
   console.log(this) <--- undefined  
    ....
}

@kurkle
Copy link
Member

kurkle commented Apr 3, 2021

arrow functions don't receive this.

@zibous
Copy link
Author

zibous commented Apr 3, 2021

arrow functions don't receive this.

Sorry,

const xAxisFormat = (value, index, values) => {
   console.log(this) <--- undefined  
   /// more lines comes here
}

Tooltip

/**
 * format the tooltip label
 * @param {*} context
 * @returns string
 */
const formatToolTipLabel = (context) => {
    let label = context.dataset.label || ""
    const data = context.raw
    const unit = context.dataset.unit ? ` (${context.dataset.unit})` : ""
    label += ": " + context.formattedValue + unit
    return label
}

Works with 3.0.0-rc.7

Version "3.0.1"
Render Graph Error on line : TypeError: Cannot add property font, object is not extensible

@zibous
Copy link
Author

zibous commented Apr 4, 2021

@kurkle , @etimberg

I have not found a way to access the optionsor ctx element from the scales.x.ticks callbackmethod (the format patterns for the date formatting would be saved in options).

const xAxisFormat = (tickValue, index, ticks) => {
     const dateFormatPattern = "day"  // no way to get this from the options !
    if (Number.isInteger(ticks[index].value)) {
        return formatdate(+ticks[index].value, dateFormatPattern)
    }
    return tickValue
}

So the only possibility is to create your own adapter with which the date is displayed in the user language.

Disadvantage:

  • 43KB more javascript code is needed to translate the date and time.
  • The translation needs resources and time to create the labels.

Modified date-fn custom adapter
@kurkle : - Not perfect, think it could be done better, but I couldn't find a description of the adapter and therefore the tryAndError version of the adapter:

import { _adapters } from "chart.js";
import {parse,parseISO,toDate,isValid,startOfSecond,startOfMinute,startOfHour,startOfDay,
        startOfWeek,startOfMonth,startOfQuarter,startOfYear,addMilliseconds,addSeconds,
        addMinutes,addHours,addDays,addWeeks,addMonths,addQuarters,addYears,differenceInMilliseconds,
        differenceInSeconds,differenceInMinutes,differenceInHours,differenceInDays,differenceInWeeks,
        differenceInMonths,differenceInQuarters,differenceInYears,endOfSecond,endOfMinute,endOfHour,
        endOfDay,endOfWeek,endOfMonth,endOfQuarter,endOfYear,
} from "date-fns";

const FORMATS = {
    default: "ddd, d mmmm yyyy HH:MM:ss.l",
    shortDate: "m.d.yy",
    mediumDate: "d.m.yyyy",
    longDate: "d mmmm yyyy",
    fullDate: "dddd, d mmmm yyyy",
    shortTime: "H:MM",
    mediumTime: "H:MM:ss",
    longTime: "H:MM:ss.L",
    isoDate: "yyyy-mm-dd",
    isoTime: "HH:MM:ss",
    isoDateTime: "yyyy-mm-dd'T'HH:MM:ss",
    isoUtcDateTime: "UTC:yyyy-mm-dd'T'HH:MM:ss'Z'",
    week: "W",
    quater: "Q",
    datetime: "ddd, d mmmm yyyy HH:MM:ss",
    millisecond: "H:MM:ss l",
    second: "h:mm:ss l",
    minute: "h:mm l",
    hour: "H",
    day: "d mmm",
    month: "mmm yyyy",
    year: "yyyy"
};

 * Get week number in the year.
 * @param  {Integer} [weekStart=0]  First day of the week. 0-based. 0 for Sunday, 6 for Saturday.
 * @return {Integer}                0-based number of week.
 */
Date.prototype.getWeek = function (weekStart = 1) {
    var januaryFirst = new Date(this.getFullYear(), 0, 1)
    weekStart = weekStart || 0
    return Math.floor(((this - januaryFirst) / 86400000 + januaryFirst.getDay() - weekStart) / 7)
}

/*
 * Date Format 1.2.3
 * (c) 2007-2009 Steven Levithan <stevenlevithan.com>
 * MIT license
 *
 * Includes enhancements by Scott Trenda <scott.trenda.net>
 * and Kris Kowal <cixar.com/~kris.kowal/>
 *
 * Accepts a date, a mask, or a date and a mask.
 * Returns a formatted version of the given date.
 * The date defaults to the current date/time.
 * The mask defaults to dateFormat.masks.default.
 * see: https://stevenlevithan.com/assets/misc/date.format.js
 *        https://blog.stevenlevithan.com/archives/date-time-format
 * modified for chart.js
 */

var formatdate = (function () {
  const token = /d{1,4}|m{1,4}|yy(?:yy)?|([HhMsTt])\1?|[LloSZWQw]|"[^"]*"|'[^']*'/g,
      timezone = /\b(?:[PMCEA][SDP]T|(?:Pacific|Mountain|Central|Eastern|Atlantic) (?:Standard|Daylight|Prevailing) Time|(?:GMT|UTC)(?:[-+]\d{4})?)\b/g,
      timezoneClip = /[^-+\dA-Z]/g,
      pad = function (val, len) {
          val = String(val)
          len = len || 2
          while (val.length < len) val = "0" + val
          return val
      }
  return function (date, mask, utc) {
      const dF = formatdate
      if (arguments.length == 1 && Object.prototype.toString.call(date) == "[object String]" && !/\d/.test(date)) {
          mask = date
          date = undefined
      }
      date = date ? new Date(date) : new Date()
      if (isNaN(date)) throw SyntaxError("invalid date")
      mask = String(dF.masks[mask] || mask || dF.masks["default"])
      // Allow setting the utc argument via the mask
      if (mask.slice(0, 4) == "UTC:") {
          mask = mask.slice(4)
          utc = true
      }
      const _ = utc ? "getUTC" : "get",
          d = date[_ + "Date"](),
          D = date[_ + "Day"](),
          m = date[_ + "Month"](),
          y = date[_ + "FullYear"](),
          H = date[_ + "Hours"](),
          M = date[_ + "Minutes"](),
          s = date[_ + "Seconds"](),
          L = date[_ + "Milliseconds"](),
          o = utc ? 0 : date.getTimezoneOffset(),
          flags = {
              // day
              d: d,
              dd: pad(d),
              ddd: window.localeNames.days_short[D],
              dddd: window.localeNames.days_long[D],
              // week
              W: date.getWeek(1),
              w: date.getWeek(0),
              // month
              m: m + 1,
              mm: pad(m + 1),
              mmm: window.localeNames.month_short[m],
              mmmm: window.localeNames.month_long[m],
              // year
              yy: String(y).slice(2),
              yyyy: y,
              Q: "Q" + Math.floor(((date.getMonth() / 3) % 4) + 1),
              // time hour
              h: H % 12 || 12,
              hh: pad(H % 12 || 12),
              H: H,
              HH: pad(H),
              // time minute
              M: M,
              MM: pad(M),
              // time seconds
              s: s,
              ss: pad(s),
              // time Milliseconds
              l: pad(L, 3),
              L: pad(L > 99 ? Math.round(L / 10) : L),
              // time am / pm
              t: H < 12 ? "a" : "p",
              tt: H < 12 ? "am" : "pm",
              T: H < 12 ? "A" : "P",
              TT: H < 12 ? "AM" : "PM",
              // GMT/UTC timezone
              Z: utc ? "UTC" : (String(date).match(timezone) || [""]).pop().replace(timezoneClip, ""),
              o: (o > 0 ? "-" : "+") + pad(Math.floor(Math.abs(o) / 60) * 100 + (Math.abs(o) % 60), 4)
          }

      return mask.replace(token, function ($0) {
          return $0 in flags ? flags[$0] : $0.slice(1, $0.length - 1)
      })
  }
})()
formatdate.masks = FORMATS;

_adapters._date.override({
  _id: "date-fns",

  formats: function () {
    return FORMATS;
  },

  parse: function (value, fmt) {
    if (value === null || typeof value === "undefined") {
      return null;
    }
    const type = typeof value;
    if (type === "number" || value instanceof Date) {
      value = toDate(value);
    } else if (type === "string") {
      if (typeof fmt === "string") {
        value = parse(value, fmt, new Date(), this.options);
      } else {
        value = parseISO(value, this.options);
      }
    }
    return isValid(value) ? value.getTime() : null;
  },

  format: function (time, fmt) {
    return formatdate(new Date(time), fmt);
  },

  add: function (time, amount, unit) {
    switch (unit) {
      case "millisecond":return addMilliseconds(time, amount);
      case "second":return addSeconds(time, amount);
      case "minute":return addMinutes(time, amount);
      case "hour":return addHours(time, amount);
      case "day":return addDays(time, amount);
      case "week":return addWeeks(time, amount);
      case "month":return addMonths(time, amount);
      case "quarter":return addQuarters(time, amount);
      case "year":return addYears(time, amount);
      default:return time;
    }
  },

  diff: function (max, min, unit) {
    switch (unit) {
      case "millisecond":return differenceInMilliseconds(max, min);
      case "second":return differenceInSeconds(max, min);
      case "minute":return differenceInMinutes(max, min);
      case "hour":return differenceInHours(max, min);
      case "day":return differenceInDays(max, min);
      case "week":return differenceInWeeks(max, min);
      case "month":return differenceInMonths(max, min);
      case "quarter":return differenceInQuarters(max, min);
      case "year":return differenceInYears(max, min);
      default:return 0;
    }
  },

  startOf: function (time, unit, weekday) {
    switch (unit) {
      case "second":return startOfSecond(time);
      case "minute":return startOfMinute(time);
      case "hour":return startOfHour(time);
      case "day":return startOfDay(time);
      case "week":return startOfWeek(time);
      case "isoWeek":return startOfWeek(time, { weekStartsOn: +weekday });
      case "month":return startOfMonth(time);
      case "quarter":return startOfQuarter(time);
      case "year":return startOfYear(time);
      default:return time;
    }
  },

  endOf: function (time, unit) {
    switch (unit) {
      case "second":return endOfSecond(time);
      case "minute":return endOfMinute(time);
      case "hour":return endOfHour(time);
      case "day":return endOfDay(time);
      case "week":return endOfWeek(time);
      case "month":return endOfMonth(time);
      case "quarter":return endOfQuarter(time);
      case "year":return endOfYear(time);
      default:return time;
    }
  },
});

I load the translations for the day and month names once when I start the application.

/**
 * set the local text for the day- and monthnames
 */
function getLocaleText(locale = "DE") {
    return {
        days_short: [...Array(7).keys()].map((day) =>
            new Intl.DateTimeFormat(locale, { weekday: "short" }).format(new Date(Date.UTC(2021, 5, day)))
        ),
        days_long: [...Array(7).keys()].map((day) =>
            new Intl.DateTimeFormat(locale, { weekday: "long" }).format(new Date(Date.UTC(2021, 5, day)))
        ),
        month_short: [...Array(12).keys()].map((month) =>
            new Intl.DateTimeFormat(locale, { month: "short" }).format(new Date(Date.UTC(2021, month, 1)))
        ),
        month_long: [...Array(12).keys()].map((month) =>
            new Intl.DateTimeFormat(locale, { month: "long" }).format(new Date(Date.UTC(2021, month, 1)))
        )
    }
}
window.localeNames = getLocaleText(window.Chart3.defaults.locale)

It would be much more performant and easier if the labels could be used in the dataset. If x-label are available, they will be displayed instead of the value (x).

[
    {
        "x": 1616968800000,
        "x-label": "2021-03-29",
        "y": 0.24
    },
    {
        "x": 1617055200000,
        "x-label": "2021-03-30",
        "y": 0.22
    },
    {
        "x": 1617141600000,
        "x-label": "2021-03-31",
        "y": 2.86
    },
    {
        "x": 1617228000000,
        "x-label": "2021-04-01",
        "y": 0.14
    },
    {
        "x": 1617314400000,
        "x-label": "2021-04-02",
        "y": 0.21
    },
    {
        "x": 1617400800000,
        "x-label": "2021-04-03",
        "y": 0.09
    },
    {
        "x": 1617487200000,
        "x-label": "2021-04-04",
        "y": 0.25
    }
]

@kurkle
Copy link
Member

kurkle commented Apr 4, 2021

This is an arrow function, it is not bound and can not use this. Please read about arrow functions in MDN

const xAxisFormat = (tickValue, index, ticks) => {
     const dateFormatPattern = "day"  // no way to get this from the options !
    if (Number.isInteger(ticks[index].value)) {
        return formatdate(+ticks[index].value, dateFormatPattern)
    }
    return tickValue
}

This is an traditional function and does not have the same limitations. It has the same name, so nothing else should need to be changed, unless something in your environment limits you from using normal/traditional functions?

function xAxisFormat(tickValue, index, ticks) {
     const dateFormatPattern = this.options.time.unit; // "day"
    if (Number.isInteger(ticks[index].value)) {
        return formatdate(+ticks[index].value, dateFormatPattern)
    }
    return tickValue
}

@zibous
Copy link
Author

zibous commented Apr 4, 2021

@kurkle
Negativ, do not work.

options.scales.x.ticks = { callback: xAxisFormat}

Bildschirmfoto 2021-04-04 um 15 01 39

@kurkle
Copy link
Member

kurkle commented Apr 4, 2021

Why don't you try to use the traditional function instead of the arrow function as I've suggested 2 times already?

image

@zibous
Copy link
Author

zibous commented Apr 4, 2021

@kurkle
Mea culpa... but also traditional function this = undefined.

Bildschirmfoto 2021-04-04 um 15 10 40

options.scales.x.ticks = { callback: xAxisFormat }

@kurkle
Copy link
Member

kurkle commented Apr 4, 2021

Are you processing your package somehow?

https://codepen.io/kurkle/pen/XWpgWzb?editors=1010

@zibous
Copy link
Author

zibous commented Apr 4, 2021

@kurkle

Bildschirmfoto 2021-04-04 um 16 11 14

file: graphchart.js

// -------------------
        // just for testing...
        // -------------------        
        if (window.configdata) {
            _options.scales = _options.scales || {}
            _options.scales.x = _options.scales.x || {}
            _options.scales.x.type = "time"
            _options.scales.x.time = {
                unit: "day",
                displayFormats: {}                
            }
            _options.scales.x.ticks = {
                callback: function (value, index, ticks) {
                    if(this){
                        console.log("THIS READY:",this)
                        return "OK " + value
                    }else{
                        console.log("THIS NOT PRESENT:",this)
                        return "FAILED " + value
                    }
                    
                }
            }
        }

see: http://www.ipscon.com/test/testcase2.html

@kurkle
Copy link
Member

kurkle commented Apr 4, 2021

ok, so its the 'time' scale that calls the callback differently: https://codepen.io/kurkle/pen/XWpgWzb?editors=1010

@kurkle
Copy link
Member

kurkle commented Apr 4, 2021

I thought you were going to use linear. Anyway, that is a bug.

@zibous
Copy link
Author

zibous commented Apr 4, 2021

I thought you were going to use linear. Anyway, that is a bug.

But this 👍 is present !
Negativ, testet but there i have problems with the x-Axis labels...

Bildschirmfoto 2021-04-04 um 16 30 09

Thanks for your help and time 👍

@kurkle
Copy link
Member

kurkle commented Apr 4, 2021

I think in your use case, it would be better (and faster) to override the generateTickLabels function in time scale:

generateTickLabels(ticks) {
let i, ilen, tick;
for (i = 0, ilen = ticks.length; i < ilen; ++i) {
tick = ticks[i];
tick.label = this._tickFormatFunction(tick.value, i, ticks);
}
}

Chart.TimeScale.prototype.generateTickLabels = function(ticks) { 
/* loop the ticks and set .label */ 
};

That could break when Chart.js v4 comes out, so should have a note about that in your code.

@zibous
Copy link
Author

zibous commented Apr 4, 2021

@kurkle

I think in your use case, it would be better (and faster) to override the generateTickLabels function in time scale:

An interesting approach, but it is too specific and not generally applicable.
I don't have to do that for the charts where categories are used. Actually, I only need it for labels that are a date.

If the callback bug is fixed with options.scales.x.type =" time ", then that would be the best solution.

I just think I'm not alone with the problem, it affects everyone who wants to display dates in their locale.
That would only be consistent, because it works great with the numbers.

Tooltip Render Graph Error on line : TypeError: Cannot add property font, object is not extensible

Has been fixed, no longer occurs in the current version.

@kurkle
Copy link
Member

kurkle commented Apr 4, 2021

If the callback bug is fixed with options.scales.x.type =" time ", then that would be the best solution.

This is fixed by #8822, and will be available in 3.1

@zibous
Copy link
Author

zibous commented Apr 4, 2021

This is fixed by #8822, and will be available in 3.1

Thanks, works now.

@zibous
Copy link
Author

zibous commented Apr 5, 2021

@kurkle

timeseries
the grid columns are too big at the beginning and at the end or am I making a mistake?

Bildschirmfoto 2021-04-05 um 10 25 30

**
 * format the x-axis date/time label
 * @param {*} tickValue
 * @param {*} index
 * @param {*} ticks
 * @returns formatted tick value
 */
 function xAxisFormat(tickValue, index, ticks) {
    console.log("xAxisFormat", this.options)
    if (this && this.options.time && this.options.time.unit) {
        const dateFormatPattern = this.options.time.unit
        if (dateFormatPattern && Number.isInteger(ticks[index].value)) {
            return formatdate(+ticks[index].value, dateFormatPattern)
        }
    }
    return tickValue
}
{
    "type": "bar",
    "data": {
        "datasets": [
            {
                "label": "Verbrauch",
                "unit": "kWh",
                "minval": 0,
                "maxval": 0,
                "sumval": 0,
                "avgval": 0,
                "pointRadius": 0,
                "current": -0.6300000000000001,
                "last_changed": "2021-04-04T13:45:19.303440+00:00",
                "mode": "history",
                "backgroundColor": "#FF8066",
                "data": [
                    {
                        "x": 1617055200000,
                        "localedate": "2021-03-30",
                        "y": -128.12
                    },
                    {
                        "x": 1617141600000,
                        "localedate": "2021-03-31",
                        "y": -160.55
                    },
                    {
                        "x": 1617228000000,
                        "localedate": "2021-04-01",
                        "y": -153.49
                    },
                    {
                        "x": 1617314400000,
                        "localedate": "2021-04-02",
                        "y": -142.32
                    },
                    {
                        "x": 1617400800000,
                        "localedate": "2021-04-03",
                        "y": -189.55
                    },
                    {
                        "x": 1617487200000,
                        "localedate": "2021-04-04",
                        "y": -75.08
                    }
                ]
            },
            {
                "label": "Energieproduktion",
                "unit": "kWh",
                "minval": 0,
                "maxval": 0,
                "sumval": 0,
                "avgval": 0,
                "pointRadius": 0,
                "current": 0,
                "last_changed": "2021-04-04T13:45:17.876805+00:00",
                "mode": "history",
                "backgroundColor": "#fcec34",
                "data": [
                    {
                        "x": 1617055200000,
                        "localedate": "2021-03-30",
                        "y": 99.891
                    },
                    {
                        "x": 1617141600000,
                        "localedate": "2021-03-31",
                        "y": 94.916
                    },
                    {
                        "x": 1617228000000,
                        "localedate": "2021-04-01",
                        "y": 106.155
                    },
                    {
                        "x": 1617314400000,
                        "localedate": "2021-04-02",
                        "y": 99.444
                    },
                    {
                        "x": 1617400800000,
                        "localedate": "2021-04-03",
                        "y": 73.143
                    },
                    {
                        "x": 1617487200000,
                        "localedate": "2021-04-04",
                        "y": 92.939
                    }
                ]
            }
        ],
        "labels": []
    },
    "options": {
        "units": "",
        "hoverOffset": 8,
        "layout": {},
        "interaction": {
            "mode": "nearest",
            "intersect": false
        },
        "chartArea": {
            "backgroundColor": "transparent"
        },
        "elements": {},
        "spanGaps": true,
        "plugins": {
            "title": {},
            "tooltip": {
                "callbacks": {}
            },
            "legend": {
                "display": true,
                "position": "top"
            }
        },
        "animation": {},
        "onResize": null,
        "scales": {
            "x": {
                "axis": "x",
                "type": "timeseries",                
                "ticks": {
                    "source": "auto",
                    "major": {
                        "enabled": false
                    },
                    "minRotation": 0,
                    "maxRotation": 50,
                    "mirror": false,
                    "textStrokeWidth": 0,
                    "textStrokeColor": "",
                    "padding": 3,
                    "display": true,
                    "autoSkip": true,
                    "autoSkipPadding": 3,
                    "labelOffset": 0,
                    "minor": {},
                    "align": "center",
                    "crossAlign": "near",
                    "color": "rgb(225, 225, 225)"
                },
                "alignToPixels": true,
                "stacked": true,
                "offset": true,
                "title": {
                    "display": true,
                    "text": "Zeitraum",
                    "padding": {
                        "top": 4,
                        "bottom": 4
                    },
                    "color": "rgb(225, 225, 225)"
                },
                "grid": {
                    "offset": true,
                    "display": true,
                    "drawBorder": true,
                    "drawOnChartArea": true,
                    "drawTicks": true,
                    "tickLength": 8,
                    "borderDash": [
                        1,
                        1
                    ],
                    "borderDashOffset": 0,
                    "borderColor": "rgb(2, 136, 209)",
                    "borderWidth": 0.88,
                    "color": "rgba(179, 229, 252, 0.8)"
                },
                "bounds": "data",
                "adapters": {},
                "display": true,
                "reverse": false,
                "beginAtZero": false,
                "grace": 0,
                "id": "x",
                "position": "bottom"
            },
            "y": {
                "axis": "y",
                "alignToPixels": true,
                "stacked": true,
                "title": {
                    "display": true,
                    "text": "Energie kWh",
                    "padding": {
                        "top": 4,
                        "bottom": 4
                    },
                    "color": "rgb(225, 225, 225)"
                },
                "type": "linear",
                "beginAtZero": true,
                "ticks": {
                    "minRotation": 0,
                    "maxRotation": 50,
                    "mirror": false,
                    "textStrokeWidth": 0,
                    "textStrokeColor": "",
                    "padding": 3,
                    "display": true,
                    "autoSkip": true,
                    "autoSkipPadding": 3,
                    "labelOffset": 0,
                    "minor": {},
                    "major": {},
                    "align": "center",
                    "crossAlign": "near",
                    "color": "rgb(225, 225, 225)"
                },
                "display": true,
                "offset": false,
                "reverse": false,
                "bounds": "ticks",
                "grace": 0,
                "grid": {
                    "display": true,
                    "drawBorder": true,
                    "drawOnChartArea": true,
                    "drawTicks": true,
                    "tickLength": 8,
                    "offset": false,
                    "borderDash": [
                        1,
                        1
                    ],
                    "borderDashOffset": 0,
                    "borderColor": "rgb(2, 136, 209)",
                    "borderWidth": 0.88,
                    "color": "rgba(179, 229, 252, 0.8)"
                },
                "id": "y",
                "position": "left"
            }
        }
    }
}

@kurkle kurkle closed this as completed Apr 10, 2021
@chartjs chartjs locked and limited conversation to collaborators Apr 10, 2021

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

Projects
None yet
Development

No branches or pull requests

3 participants