'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); const index = require('./index-9f97b469.js'); const debounce = require('./debounce-7d86d183.js'); const groupConfigUtils = require('./group-config-utils-07ac5e65.js'); const uniqueId = require('./unique-id-292f555d.js'); const chartState = require('./chart-state-0a988421.js'); const math = require('./math-2614d9ef.js'); const array = require('./array-490698ca.js'); function nice(domain, interval) { domain = domain.slice(); var i0 = 0, i1 = domain.length - 1, x0 = domain[i0], x1 = domain[i1], t; if (x1 < x0) { t = i0, i0 = i1, i1 = t; t = x0, x0 = x1, x1 = t; } domain[i0] = interval.floor(x0); domain[i1] = interval.ceil(x1); return domain; } var t0 = new Date, t1 = new Date; function newInterval(floori, offseti, count, field) { function interval(date) { return floori(date = arguments.length === 0 ? new Date : new Date(+date)), date; } interval.floor = function(date) { return floori(date = new Date(+date)), date; }; interval.ceil = function(date) { return floori(date = new Date(date - 1)), offseti(date, 1), floori(date), date; }; interval.round = function(date) { var d0 = interval(date), d1 = interval.ceil(date); return date - d0 < d1 - date ? d0 : d1; }; interval.offset = function(date, step) { return offseti(date = new Date(+date), step == null ? 1 : Math.floor(step)), date; }; interval.range = function(start, stop, step) { var range = [], previous; start = interval.ceil(start); step = step == null ? 1 : Math.floor(step); if (!(start < stop) || !(step > 0)) return range; // also handles Invalid Date do range.push(previous = new Date(+start)), offseti(start, step), floori(start); while (previous < start && start < stop); return range; }; interval.filter = function(test) { return newInterval(function(date) { if (date >= date) while (floori(date), !test(date)) date.setTime(date - 1); }, function(date, step) { if (date >= date) { if (step < 0) while (++step <= 0) { while (offseti(date, -1), !test(date)) {} // eslint-disable-line no-empty } else while (--step >= 0) { while (offseti(date, +1), !test(date)) {} // eslint-disable-line no-empty } } }); }; if (count) { interval.count = function(start, end) { t0.setTime(+start), t1.setTime(+end); floori(t0), floori(t1); return Math.floor(count(t0, t1)); }; interval.every = function(step) { step = Math.floor(step); return !isFinite(step) || !(step > 0) ? null : !(step > 1) ? interval : interval.filter(field ? function(d) { return field(d) % step === 0; } : function(d) { return interval.count(0, d) % step === 0; }); }; } return interval; } var millisecond = newInterval(function() { // noop }, function(date, step) { date.setTime(+date + step); }, function(start, end) { return end - start; }); // An optimized implementation for this simple case. millisecond.every = function(k) { k = Math.floor(k); if (!isFinite(k) || !(k > 0)) return null; if (!(k > 1)) return millisecond; return newInterval(function(date) { date.setTime(Math.floor(date / k) * k); }, function(date, step) { date.setTime(+date + step * k); }, function(start, end) { return (end - start) / k; }); }; var durationSecond$1 = 1e3; var durationMinute$1 = 6e4; var durationHour$1 = 36e5; var durationDay$1 = 864e5; var durationWeek$1 = 6048e5; var second = newInterval(function(date) { date.setTime(date - date.getMilliseconds()); }, function(date, step) { date.setTime(+date + step * durationSecond$1); }, function(start, end) { return (end - start) / durationSecond$1; }, function(date) { return date.getUTCSeconds(); }); var minute = newInterval(function(date) { date.setTime(date - date.getMilliseconds() - date.getSeconds() * durationSecond$1); }, function(date, step) { date.setTime(+date + step * durationMinute$1); }, function(start, end) { return (end - start) / durationMinute$1; }, function(date) { return date.getMinutes(); }); var hour = newInterval(function(date) { date.setTime(date - date.getMilliseconds() - date.getSeconds() * durationSecond$1 - date.getMinutes() * durationMinute$1); }, function(date, step) { date.setTime(+date + step * durationHour$1); }, function(start, end) { return (end - start) / durationHour$1; }, function(date) { return date.getHours(); }); var day = newInterval( date => date.setHours(0, 0, 0, 0), (date, step) => date.setDate(date.getDate() + step), (start, end) => (end - start - (end.getTimezoneOffset() - start.getTimezoneOffset()) * durationMinute$1) / durationDay$1, date => date.getDate() - 1 ); function weekday(i) { return newInterval(function(date) { date.setDate(date.getDate() - (date.getDay() + 7 - i) % 7); date.setHours(0, 0, 0, 0); }, function(date, step) { date.setDate(date.getDate() + step * 7); }, function(start, end) { return (end - start - (end.getTimezoneOffset() - start.getTimezoneOffset()) * durationMinute$1) / durationWeek$1; }); } var sunday = weekday(0); var monday = weekday(1); weekday(2); weekday(3); var thursday = weekday(4); weekday(5); weekday(6); var month = newInterval(function(date) { date.setDate(1); date.setHours(0, 0, 0, 0); }, function(date, step) { date.setMonth(date.getMonth() + step); }, function(start, end) { return end.getMonth() - start.getMonth() + (end.getFullYear() - start.getFullYear()) * 12; }, function(date) { return date.getMonth(); }); var year = newInterval(function(date) { date.setMonth(0, 1); date.setHours(0, 0, 0, 0); }, function(date, step) { date.setFullYear(date.getFullYear() + step); }, function(start, end) { return end.getFullYear() - start.getFullYear(); }, function(date) { return date.getFullYear(); }); // An optimized implementation for this simple case. year.every = function(k) { return !isFinite(k = Math.floor(k)) || !(k > 0) ? null : newInterval(function(date) { date.setFullYear(Math.floor(date.getFullYear() / k) * k); date.setMonth(0, 1); date.setHours(0, 0, 0, 0); }, function(date, step) { date.setFullYear(date.getFullYear() + step * k); }); }; var utcDay = newInterval(function(date) { date.setUTCHours(0, 0, 0, 0); }, function(date, step) { date.setUTCDate(date.getUTCDate() + step); }, function(start, end) { return (end - start) / durationDay$1; }, function(date) { return date.getUTCDate() - 1; }); function utcWeekday(i) { return newInterval(function(date) { date.setUTCDate(date.getUTCDate() - (date.getUTCDay() + 7 - i) % 7); date.setUTCHours(0, 0, 0, 0); }, function(date, step) { date.setUTCDate(date.getUTCDate() + step * 7); }, function(start, end) { return (end - start) / durationWeek$1; }); } var utcSunday = utcWeekday(0); var utcMonday = utcWeekday(1); utcWeekday(2); utcWeekday(3); var utcThursday = utcWeekday(4); utcWeekday(5); utcWeekday(6); var utcYear = newInterval(function(date) { date.setUTCMonth(0, 1); date.setUTCHours(0, 0, 0, 0); }, function(date, step) { date.setUTCFullYear(date.getUTCFullYear() + step); }, function(start, end) { return end.getUTCFullYear() - start.getUTCFullYear(); }, function(date) { return date.getUTCFullYear(); }); // An optimized implementation for this simple case. utcYear.every = function(k) { return !isFinite(k = Math.floor(k)) || !(k > 0) ? null : newInterval(function(date) { date.setUTCFullYear(Math.floor(date.getUTCFullYear() / k) * k); date.setUTCMonth(0, 1); date.setUTCHours(0, 0, 0, 0); }, function(date, step) { date.setUTCFullYear(date.getUTCFullYear() + step * k); }); }; function localDate(d) { if (0 <= d.y && d.y < 100) { var date = new Date(-1, d.m, d.d, d.H, d.M, d.S, d.L); date.setFullYear(d.y); return date; } return new Date(d.y, d.m, d.d, d.H, d.M, d.S, d.L); } function utcDate(d) { if (0 <= d.y && d.y < 100) { var date = new Date(Date.UTC(-1, d.m, d.d, d.H, d.M, d.S, d.L)); date.setUTCFullYear(d.y); return date; } return new Date(Date.UTC(d.y, d.m, d.d, d.H, d.M, d.S, d.L)); } function newDate(y, m, d) { return {y: y, m: m, d: d, H: 0, M: 0, S: 0, L: 0}; } function formatLocale(locale) { var locale_dateTime = locale.dateTime, locale_date = locale.date, locale_time = locale.time, locale_periods = locale.periods, locale_weekdays = locale.days, locale_shortWeekdays = locale.shortDays, locale_months = locale.months, locale_shortMonths = locale.shortMonths; var periodRe = formatRe(locale_periods), periodLookup = formatLookup(locale_periods), weekdayRe = formatRe(locale_weekdays), weekdayLookup = formatLookup(locale_weekdays), shortWeekdayRe = formatRe(locale_shortWeekdays), shortWeekdayLookup = formatLookup(locale_shortWeekdays), monthRe = formatRe(locale_months), monthLookup = formatLookup(locale_months), shortMonthRe = formatRe(locale_shortMonths), shortMonthLookup = formatLookup(locale_shortMonths); var formats = { "a": formatShortWeekday, "A": formatWeekday, "b": formatShortMonth, "B": formatMonth, "c": null, "d": formatDayOfMonth, "e": formatDayOfMonth, "f": formatMicroseconds, "g": formatYearISO, "G": formatFullYearISO, "H": formatHour24, "I": formatHour12, "j": formatDayOfYear, "L": formatMilliseconds, "m": formatMonthNumber, "M": formatMinutes, "p": formatPeriod, "q": formatQuarter, "Q": formatUnixTimestamp, "s": formatUnixTimestampSeconds, "S": formatSeconds, "u": formatWeekdayNumberMonday, "U": formatWeekNumberSunday, "V": formatWeekNumberISO, "w": formatWeekdayNumberSunday, "W": formatWeekNumberMonday, "x": null, "X": null, "y": formatYear, "Y": formatFullYear, "Z": formatZone, "%": formatLiteralPercent }; var utcFormats = { "a": formatUTCShortWeekday, "A": formatUTCWeekday, "b": formatUTCShortMonth, "B": formatUTCMonth, "c": null, "d": formatUTCDayOfMonth, "e": formatUTCDayOfMonth, "f": formatUTCMicroseconds, "g": formatUTCYearISO, "G": formatUTCFullYearISO, "H": formatUTCHour24, "I": formatUTCHour12, "j": formatUTCDayOfYear, "L": formatUTCMilliseconds, "m": formatUTCMonthNumber, "M": formatUTCMinutes, "p": formatUTCPeriod, "q": formatUTCQuarter, "Q": formatUnixTimestamp, "s": formatUnixTimestampSeconds, "S": formatUTCSeconds, "u": formatUTCWeekdayNumberMonday, "U": formatUTCWeekNumberSunday, "V": formatUTCWeekNumberISO, "w": formatUTCWeekdayNumberSunday, "W": formatUTCWeekNumberMonday, "x": null, "X": null, "y": formatUTCYear, "Y": formatUTCFullYear, "Z": formatUTCZone, "%": formatLiteralPercent }; var parses = { "a": parseShortWeekday, "A": parseWeekday, "b": parseShortMonth, "B": parseMonth, "c": parseLocaleDateTime, "d": parseDayOfMonth, "e": parseDayOfMonth, "f": parseMicroseconds, "g": parseYear, "G": parseFullYear, "H": parseHour24, "I": parseHour24, "j": parseDayOfYear, "L": parseMilliseconds, "m": parseMonthNumber, "M": parseMinutes, "p": parsePeriod, "q": parseQuarter, "Q": parseUnixTimestamp, "s": parseUnixTimestampSeconds, "S": parseSeconds, "u": parseWeekdayNumberMonday, "U": parseWeekNumberSunday, "V": parseWeekNumberISO, "w": parseWeekdayNumberSunday, "W": parseWeekNumberMonday, "x": parseLocaleDate, "X": parseLocaleTime, "y": parseYear, "Y": parseFullYear, "Z": parseZone, "%": parseLiteralPercent }; // These recursive directive definitions must be deferred. formats.x = newFormat(locale_date, formats); formats.X = newFormat(locale_time, formats); formats.c = newFormat(locale_dateTime, formats); utcFormats.x = newFormat(locale_date, utcFormats); utcFormats.X = newFormat(locale_time, utcFormats); utcFormats.c = newFormat(locale_dateTime, utcFormats); function newFormat(specifier, formats) { return function(date) { var string = [], i = -1, j = 0, n = specifier.length, c, pad, format; if (!(date instanceof Date)) date = new Date(+date); while (++i < n) { if (specifier.charCodeAt(i) === 37) { string.push(specifier.slice(j, i)); if ((pad = pads[c = specifier.charAt(++i)]) != null) c = specifier.charAt(++i); else pad = c === "e" ? " " : "0"; if (format = formats[c]) c = format(date, pad); string.push(c); j = i + 1; } } string.push(specifier.slice(j, i)); return string.join(""); }; } function newParse(specifier, Z) { return function(string) { var d = newDate(1900, undefined, 1), i = parseSpecifier(d, specifier, string += "", 0), week, day$1; if (i != string.length) return null; // If a UNIX timestamp is specified, return it. if ("Q" in d) return new Date(d.Q); if ("s" in d) return new Date(d.s * 1000 + ("L" in d ? d.L : 0)); // If this is utcParse, never use the local timezone. if (Z && !("Z" in d)) d.Z = 0; // The am-pm flag is 0 for AM, and 1 for PM. if ("p" in d) d.H = d.H % 12 + d.p * 12; // If the month was not specified, inherit from the quarter. if (d.m === undefined) d.m = "q" in d ? d.q : 0; // Convert day-of-week and week-of-year to day-of-year. if ("V" in d) { if (d.V < 1 || d.V > 53) return null; if (!("w" in d)) d.w = 1; if ("Z" in d) { week = utcDate(newDate(d.y, 0, 1)), day$1 = week.getUTCDay(); week = day$1 > 4 || day$1 === 0 ? utcMonday.ceil(week) : utcMonday(week); week = utcDay.offset(week, (d.V - 1) * 7); d.y = week.getUTCFullYear(); d.m = week.getUTCMonth(); d.d = week.getUTCDate() + (d.w + 6) % 7; } else { week = localDate(newDate(d.y, 0, 1)), day$1 = week.getDay(); week = day$1 > 4 || day$1 === 0 ? monday.ceil(week) : monday(week); week = day.offset(week, (d.V - 1) * 7); d.y = week.getFullYear(); d.m = week.getMonth(); d.d = week.getDate() + (d.w + 6) % 7; } } else if ("W" in d || "U" in d) { if (!("w" in d)) d.w = "u" in d ? d.u % 7 : "W" in d ? 1 : 0; day$1 = "Z" in d ? utcDate(newDate(d.y, 0, 1)).getUTCDay() : localDate(newDate(d.y, 0, 1)).getDay(); d.m = 0; d.d = "W" in d ? (d.w + 6) % 7 + d.W * 7 - (day$1 + 5) % 7 : d.w + d.U * 7 - (day$1 + 6) % 7; } // If a time zone is specified, all fields are interpreted as UTC and then // offset according to the specified time zone. if ("Z" in d) { d.H += d.Z / 100 | 0; d.M += d.Z % 100; return utcDate(d); } // Otherwise, all fields are in local time. return localDate(d); }; } function parseSpecifier(d, specifier, string, j) { var i = 0, n = specifier.length, m = string.length, c, parse; while (i < n) { if (j >= m) return -1; c = specifier.charCodeAt(i++); if (c === 37) { c = specifier.charAt(i++); parse = parses[c in pads ? specifier.charAt(i++) : c]; if (!parse || ((j = parse(d, string, j)) < 0)) return -1; } else if (c != string.charCodeAt(j++)) { return -1; } } return j; } function parsePeriod(d, string, i) { var n = periodRe.exec(string.slice(i)); return n ? (d.p = periodLookup.get(n[0].toLowerCase()), i + n[0].length) : -1; } function parseShortWeekday(d, string, i) { var n = shortWeekdayRe.exec(string.slice(i)); return n ? (d.w = shortWeekdayLookup.get(n[0].toLowerCase()), i + n[0].length) : -1; } function parseWeekday(d, string, i) { var n = weekdayRe.exec(string.slice(i)); return n ? (d.w = weekdayLookup.get(n[0].toLowerCase()), i + n[0].length) : -1; } function parseShortMonth(d, string, i) { var n = shortMonthRe.exec(string.slice(i)); return n ? (d.m = shortMonthLookup.get(n[0].toLowerCase()), i + n[0].length) : -1; } function parseMonth(d, string, i) { var n = monthRe.exec(string.slice(i)); return n ? (d.m = monthLookup.get(n[0].toLowerCase()), i + n[0].length) : -1; } function parseLocaleDateTime(d, string, i) { return parseSpecifier(d, locale_dateTime, string, i); } function parseLocaleDate(d, string, i) { return parseSpecifier(d, locale_date, string, i); } function parseLocaleTime(d, string, i) { return parseSpecifier(d, locale_time, string, i); } function formatShortWeekday(d) { return locale_shortWeekdays[d.getDay()]; } function formatWeekday(d) { return locale_weekdays[d.getDay()]; } function formatShortMonth(d) { return locale_shortMonths[d.getMonth()]; } function formatMonth(d) { return locale_months[d.getMonth()]; } function formatPeriod(d) { return locale_periods[+(d.getHours() >= 12)]; } function formatQuarter(d) { return 1 + ~~(d.getMonth() / 3); } function formatUTCShortWeekday(d) { return locale_shortWeekdays[d.getUTCDay()]; } function formatUTCWeekday(d) { return locale_weekdays[d.getUTCDay()]; } function formatUTCShortMonth(d) { return locale_shortMonths[d.getUTCMonth()]; } function formatUTCMonth(d) { return locale_months[d.getUTCMonth()]; } function formatUTCPeriod(d) { return locale_periods[+(d.getUTCHours() >= 12)]; } function formatUTCQuarter(d) { return 1 + ~~(d.getUTCMonth() / 3); } return { format: function(specifier) { var f = newFormat(specifier += "", formats); f.toString = function() { return specifier; }; return f; }, parse: function(specifier) { var p = newParse(specifier += "", false); p.toString = function() { return specifier; }; return p; }, utcFormat: function(specifier) { var f = newFormat(specifier += "", utcFormats); f.toString = function() { return specifier; }; return f; }, utcParse: function(specifier) { var p = newParse(specifier += "", true); p.toString = function() { return specifier; }; return p; } }; } var pads = {"-": "", "_": " ", "0": "0"}, numberRe = /^\s*\d+/, // note: ignores next directive percentRe = /^%/, requoteRe = /[\\^$*+?|[\]().{}]/g; function pad(value, fill, width) { var sign = value < 0 ? "-" : "", string = (sign ? -value : value) + "", length = string.length; return sign + (length < width ? new Array(width - length + 1).join(fill) + string : string); } function requote(s) { return s.replace(requoteRe, "\\$&"); } function formatRe(names) { return new RegExp("^(?:" + names.map(requote).join("|") + ")", "i"); } function formatLookup(names) { return new Map(names.map((name, i) => [name.toLowerCase(), i])); } function parseWeekdayNumberSunday(d, string, i) { var n = numberRe.exec(string.slice(i, i + 1)); return n ? (d.w = +n[0], i + n[0].length) : -1; } function parseWeekdayNumberMonday(d, string, i) { var n = numberRe.exec(string.slice(i, i + 1)); return n ? (d.u = +n[0], i + n[0].length) : -1; } function parseWeekNumberSunday(d, string, i) { var n = numberRe.exec(string.slice(i, i + 2)); return n ? (d.U = +n[0], i + n[0].length) : -1; } function parseWeekNumberISO(d, string, i) { var n = numberRe.exec(string.slice(i, i + 2)); return n ? (d.V = +n[0], i + n[0].length) : -1; } function parseWeekNumberMonday(d, string, i) { var n = numberRe.exec(string.slice(i, i + 2)); return n ? (d.W = +n[0], i + n[0].length) : -1; } function parseFullYear(d, string, i) { var n = numberRe.exec(string.slice(i, i + 4)); return n ? (d.y = +n[0], i + n[0].length) : -1; } function parseYear(d, string, i) { var n = numberRe.exec(string.slice(i, i + 2)); return n ? (d.y = +n[0] + (+n[0] > 68 ? 1900 : 2000), i + n[0].length) : -1; } function parseZone(d, string, i) { var n = /^(Z)|([+-]\d\d)(?::?(\d\d))?/.exec(string.slice(i, i + 6)); return n ? (d.Z = n[1] ? 0 : -(n[2] + (n[3] || "00")), i + n[0].length) : -1; } function parseQuarter(d, string, i) { var n = numberRe.exec(string.slice(i, i + 1)); return n ? (d.q = n[0] * 3 - 3, i + n[0].length) : -1; } function parseMonthNumber(d, string, i) { var n = numberRe.exec(string.slice(i, i + 2)); return n ? (d.m = n[0] - 1, i + n[0].length) : -1; } function parseDayOfMonth(d, string, i) { var n = numberRe.exec(string.slice(i, i + 2)); return n ? (d.d = +n[0], i + n[0].length) : -1; } function parseDayOfYear(d, string, i) { var n = numberRe.exec(string.slice(i, i + 3)); return n ? (d.m = 0, d.d = +n[0], i + n[0].length) : -1; } function parseHour24(d, string, i) { var n = numberRe.exec(string.slice(i, i + 2)); return n ? (d.H = +n[0], i + n[0].length) : -1; } function parseMinutes(d, string, i) { var n = numberRe.exec(string.slice(i, i + 2)); return n ? (d.M = +n[0], i + n[0].length) : -1; } function parseSeconds(d, string, i) { var n = numberRe.exec(string.slice(i, i + 2)); return n ? (d.S = +n[0], i + n[0].length) : -1; } function parseMilliseconds(d, string, i) { var n = numberRe.exec(string.slice(i, i + 3)); return n ? (d.L = +n[0], i + n[0].length) : -1; } function parseMicroseconds(d, string, i) { var n = numberRe.exec(string.slice(i, i + 6)); return n ? (d.L = Math.floor(n[0] / 1000), i + n[0].length) : -1; } function parseLiteralPercent(d, string, i) { var n = percentRe.exec(string.slice(i, i + 1)); return n ? i + n[0].length : -1; } function parseUnixTimestamp(d, string, i) { var n = numberRe.exec(string.slice(i)); return n ? (d.Q = +n[0], i + n[0].length) : -1; } function parseUnixTimestampSeconds(d, string, i) { var n = numberRe.exec(string.slice(i)); return n ? (d.s = +n[0], i + n[0].length) : -1; } function formatDayOfMonth(d, p) { return pad(d.getDate(), p, 2); } function formatHour24(d, p) { return pad(d.getHours(), p, 2); } function formatHour12(d, p) { return pad(d.getHours() % 12 || 12, p, 2); } function formatDayOfYear(d, p) { return pad(1 + day.count(year(d), d), p, 3); } function formatMilliseconds(d, p) { return pad(d.getMilliseconds(), p, 3); } function formatMicroseconds(d, p) { return formatMilliseconds(d, p) + "000"; } function formatMonthNumber(d, p) { return pad(d.getMonth() + 1, p, 2); } function formatMinutes(d, p) { return pad(d.getMinutes(), p, 2); } function formatSeconds(d, p) { return pad(d.getSeconds(), p, 2); } function formatWeekdayNumberMonday(d) { var day = d.getDay(); return day === 0 ? 7 : day; } function formatWeekNumberSunday(d, p) { return pad(sunday.count(year(d) - 1, d), p, 2); } function dISO(d) { var day = d.getDay(); return (day >= 4 || day === 0) ? thursday(d) : thursday.ceil(d); } function formatWeekNumberISO(d, p) { d = dISO(d); return pad(thursday.count(year(d), d) + (year(d).getDay() === 4), p, 2); } function formatWeekdayNumberSunday(d) { return d.getDay(); } function formatWeekNumberMonday(d, p) { return pad(monday.count(year(d) - 1, d), p, 2); } function formatYear(d, p) { return pad(d.getFullYear() % 100, p, 2); } function formatYearISO(d, p) { d = dISO(d); return pad(d.getFullYear() % 100, p, 2); } function formatFullYear(d, p) { return pad(d.getFullYear() % 10000, p, 4); } function formatFullYearISO(d, p) { var day = d.getDay(); d = (day >= 4 || day === 0) ? thursday(d) : thursday.ceil(d); return pad(d.getFullYear() % 10000, p, 4); } function formatZone(d) { var z = d.getTimezoneOffset(); return (z > 0 ? "-" : (z *= -1, "+")) + pad(z / 60 | 0, "0", 2) + pad(z % 60, "0", 2); } function formatUTCDayOfMonth(d, p) { return pad(d.getUTCDate(), p, 2); } function formatUTCHour24(d, p) { return pad(d.getUTCHours(), p, 2); } function formatUTCHour12(d, p) { return pad(d.getUTCHours() % 12 || 12, p, 2); } function formatUTCDayOfYear(d, p) { return pad(1 + utcDay.count(utcYear(d), d), p, 3); } function formatUTCMilliseconds(d, p) { return pad(d.getUTCMilliseconds(), p, 3); } function formatUTCMicroseconds(d, p) { return formatUTCMilliseconds(d, p) + "000"; } function formatUTCMonthNumber(d, p) { return pad(d.getUTCMonth() + 1, p, 2); } function formatUTCMinutes(d, p) { return pad(d.getUTCMinutes(), p, 2); } function formatUTCSeconds(d, p) { return pad(d.getUTCSeconds(), p, 2); } function formatUTCWeekdayNumberMonday(d) { var dow = d.getUTCDay(); return dow === 0 ? 7 : dow; } function formatUTCWeekNumberSunday(d, p) { return pad(utcSunday.count(utcYear(d) - 1, d), p, 2); } function UTCdISO(d) { var day = d.getUTCDay(); return (day >= 4 || day === 0) ? utcThursday(d) : utcThursday.ceil(d); } function formatUTCWeekNumberISO(d, p) { d = UTCdISO(d); return pad(utcThursday.count(utcYear(d), d) + (utcYear(d).getUTCDay() === 4), p, 2); } function formatUTCWeekdayNumberSunday(d) { return d.getUTCDay(); } function formatUTCWeekNumberMonday(d, p) { return pad(utcMonday.count(utcYear(d) - 1, d), p, 2); } function formatUTCYear(d, p) { return pad(d.getUTCFullYear() % 100, p, 2); } function formatUTCYearISO(d, p) { d = UTCdISO(d); return pad(d.getUTCFullYear() % 100, p, 2); } function formatUTCFullYear(d, p) { return pad(d.getUTCFullYear() % 10000, p, 4); } function formatUTCFullYearISO(d, p) { var day = d.getUTCDay(); d = (day >= 4 || day === 0) ? utcThursday(d) : utcThursday.ceil(d); return pad(d.getUTCFullYear() % 10000, p, 4); } function formatUTCZone() { return "+0000"; } function formatLiteralPercent() { return "%"; } function formatUnixTimestamp(d) { return +d; } function formatUnixTimestampSeconds(d) { return Math.floor(+d / 1000); } var locale; var timeFormat; defaultLocale({ dateTime: "%x, %X", date: "%-m/%-d/%Y", time: "%-I:%M:%S %p", periods: ["AM", "PM"], days: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"], shortDays: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"], months: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"], shortMonths: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] }); function defaultLocale(definition) { locale = formatLocale(definition); timeFormat = locale.format; return locale; } var durationSecond = 1000, durationMinute = durationSecond * 60, durationHour = durationMinute * 60, durationDay = durationHour * 24, durationWeek = durationDay * 7, durationMonth = durationDay * 30, durationYear = durationDay * 365; function date(t) { return new Date(t); } function number(t) { return t instanceof Date ? +t : +new Date(+t); } function calendar(year, month, week, day, hour, minute, second, millisecond, format) { var scale = groupConfigUtils.continuous(), invert = scale.invert, domain = scale.domain; var formatMillisecond = format(".%L"), formatSecond = format(":%S"), formatMinute = format("%I:%M"), formatHour = format("%I %p"), formatDay = format("%a %d"), formatWeek = format("%b %d"), formatMonth = format("%B"), formatYear = format("%Y"); var tickIntervals = [ [second, 1, durationSecond], [second, 5, 5 * durationSecond], [second, 15, 15 * durationSecond], [second, 30, 30 * durationSecond], [minute, 1, durationMinute], [minute, 5, 5 * durationMinute], [minute, 15, 15 * durationMinute], [minute, 30, 30 * durationMinute], [ hour, 1, durationHour ], [ hour, 3, 3 * durationHour ], [ hour, 6, 6 * durationHour ], [ hour, 12, 12 * durationHour ], [ day, 1, durationDay ], [ day, 2, 2 * durationDay ], [ week, 1, durationWeek ], [ month, 1, durationMonth ], [ month, 3, 3 * durationMonth ], [ year, 1, durationYear ] ]; function tickFormat(date) { return (second(date) < date ? formatMillisecond : minute(date) < date ? formatSecond : hour(date) < date ? formatMinute : day(date) < date ? formatHour : month(date) < date ? (week(date) < date ? formatDay : formatWeek) : year(date) < date ? formatMonth : formatYear)(date); } function tickInterval(interval, start, stop) { if (interval == null) interval = 10; // If a desired tick count is specified, pick a reasonable tick interval // based on the extent of the domain and a rough estimate of tick size. // Otherwise, assume interval is already a time interval and use it. if (typeof interval === "number") { var target = Math.abs(stop - start) / interval, i = groupConfigUtils.bisector(function(i) { return i[2]; }).right(tickIntervals, target), step; if (i === tickIntervals.length) { step = groupConfigUtils.tickStep(start / durationYear, stop / durationYear, interval); interval = year; } else if (i) { i = tickIntervals[target / tickIntervals[i - 1][2] < tickIntervals[i][2] / target ? i - 1 : i]; step = i[1]; interval = i[0]; } else { step = Math.max(groupConfigUtils.tickStep(start, stop, interval), 1); interval = millisecond; } return interval.every(step); } return interval; } scale.invert = function(y) { return new Date(invert(y)); }; scale.domain = function(_) { return arguments.length ? domain(Array.from(_, number)) : domain().map(date); }; scale.ticks = function(interval) { var d = domain(), t0 = d[0], t1 = d[d.length - 1], r = t1 < t0, t; if (r) t = t0, t0 = t1, t1 = t; t = tickInterval(interval, t0, t1); t = t ? t.range(t0, t1 + 1) : []; // inclusive stop return r ? t.reverse() : t; }; scale.tickFormat = function(count, specifier) { return specifier == null ? tickFormat : format(specifier); }; scale.nice = function(interval) { var d = domain(); return (interval = tickInterval(interval, d[0], d[d.length - 1])) ? domain(nice(d, interval)) : scale; }; scale.copy = function() { return groupConfigUtils.copy(scale, calendar(year, month, week, day, hour, minute, second, millisecond, format)); }; return scale; } function time() { return groupConfigUtils.initRange.apply(calendar(year, month, sunday, day, hour, minute, second, millisecond, timeFormat).domain([new Date(2000, 0, 1), new Date(2000, 0, 2)]), arguments); } function sourceEvent(event) { let sourceEvent; while (sourceEvent = event.sourceEvent) event = sourceEvent; return event; } function pointer(event, node) { event = sourceEvent(event); if (node === undefined) node = event.currentTarget; if (node) { var svg = node.ownerSVGElement || node; if (svg.createSVGPoint) { var point = svg.createSVGPoint(); point.x = event.clientX, point.y = event.clientY; point = point.matrixTransform(node.getScreenCTM().inverse()); return [point.x, point.y]; } if (node.getBoundingClientRect) { var rect = node.getBoundingClientRect(); return [event.clientX - rect.left - node.clientLeft, event.clientY - rect.top - node.clientTop]; } } return [event.pageX, event.pageY]; } function Linear(context) { this._context = context; } Linear.prototype = { areaStart: function() { this._line = 0; }, areaEnd: function() { this._line = NaN; }, lineStart: function() { this._point = 0; }, lineEnd: function() { if (this._line || (this._line !== 0 && this._point === 1)) this._context.closePath(); this._line = 1 - this._line; }, point: function(x, y) { x = +x, y = +y; switch (this._point) { case 0: this._point = 1; this._line ? this._context.lineTo(x, y) : this._context.moveTo(x, y); break; case 1: this._point = 2; // proceed default: this._context.lineTo(x, y); break; } } }; function curveLinear(context) { return new Linear(context); } function x(p) { return p[0]; } function y(p) { return p[1]; } function line(x$1, y$1) { var defined = chartState.constant(true), context = null, curve = curveLinear, output = null; x$1 = typeof x$1 === "function" ? x$1 : (x$1 === undefined) ? x : chartState.constant(x$1); y$1 = typeof y$1 === "function" ? y$1 : (y$1 === undefined) ? y : chartState.constant(y$1); function line(data) { var i, n = (data = array.array(data)).length, d, defined0 = false, buffer; if (context == null) output = curve(buffer = math.path()); for (i = 0; i <= n; ++i) { if (!(i < n && defined(d = data[i], i, data)) === defined0) { if (defined0 = !defined0) output.lineStart(); else output.lineEnd(); } if (defined0) output.point(+x$1(d, i, data), +y$1(d, i, data)); } if (buffer) return output = null, buffer + "" || null; } line.x = function(_) { return arguments.length ? (x$1 = typeof _ === "function" ? _ : chartState.constant(+_), line) : x$1; }; line.y = function(_) { return arguments.length ? (y$1 = typeof _ === "function" ? _ : chartState.constant(+_), line) : y$1; }; line.defined = function(_) { return arguments.length ? (defined = typeof _ === "function" ? _ : chartState.constant(!!_), line) : defined; }; line.curve = function(_) { return arguments.length ? (curve = _, context != null && (output = curve(context)), line) : curve; }; line.context = function(_) { return arguments.length ? (_ == null ? context = output = null : output = curve(context = _), line) : context; }; return line; } function point$3(that, x, y) { that._context.bezierCurveTo( (2 * that._x0 + that._x1) / 3, (2 * that._y0 + that._y1) / 3, (that._x0 + 2 * that._x1) / 3, (that._y0 + 2 * that._y1) / 3, (that._x0 + 4 * that._x1 + x) / 6, (that._y0 + 4 * that._y1 + y) / 6 ); } function Basis(context) { this._context = context; } Basis.prototype = { areaStart: function() { this._line = 0; }, areaEnd: function() { this._line = NaN; }, lineStart: function() { this._x0 = this._x1 = this._y0 = this._y1 = NaN; this._point = 0; }, lineEnd: function() { switch (this._point) { case 3: point$3(this, this._x1, this._y1); // proceed case 2: this._context.lineTo(this._x1, this._y1); break; } if (this._line || (this._line !== 0 && this._point === 1)) this._context.closePath(); this._line = 1 - this._line; }, point: function(x, y) { x = +x, y = +y; switch (this._point) { case 0: this._point = 1; this._line ? this._context.lineTo(x, y) : this._context.moveTo(x, y); break; case 1: this._point = 2; break; case 2: this._point = 3; this._context.lineTo((5 * this._x0 + this._x1) / 6, (5 * this._y0 + this._y1) / 6); // proceed default: point$3(this, x, y); break; } this._x0 = this._x1, this._x1 = x; this._y0 = this._y1, this._y1 = y; } }; function curveBasis(context) { return new Basis(context); } function Bundle(context, beta) { this._basis = new Basis(context); this._beta = beta; } Bundle.prototype = { lineStart: function() { this._x = []; this._y = []; this._basis.lineStart(); }, lineEnd: function() { var x = this._x, y = this._y, j = x.length - 1; if (j > 0) { var x0 = x[0], y0 = y[0], dx = x[j] - x0, dy = y[j] - y0, i = -1, t; while (++i <= j) { t = i / j; this._basis.point( this._beta * x[i] + (1 - this._beta) * (x0 + t * dx), this._beta * y[i] + (1 - this._beta) * (y0 + t * dy) ); } } this._x = this._y = null; this._basis.lineEnd(); }, point: function(x, y) { this._x.push(+x); this._y.push(+y); } }; const curveBundle = (function custom(beta) { function bundle(context) { return beta === 1 ? new Basis(context) : new Bundle(context, beta); } bundle.beta = function(beta) { return custom(+beta); }; return bundle; })(0.85); function point$2(that, x, y) { that._context.bezierCurveTo( that._x1 + that._k * (that._x2 - that._x0), that._y1 + that._k * (that._y2 - that._y0), that._x2 + that._k * (that._x1 - x), that._y2 + that._k * (that._y1 - y), that._x2, that._y2 ); } function Cardinal(context, tension) { this._context = context; this._k = (1 - tension) / 6; } Cardinal.prototype = { areaStart: function() { this._line = 0; }, areaEnd: function() { this._line = NaN; }, lineStart: function() { this._x0 = this._x1 = this._x2 = this._y0 = this._y1 = this._y2 = NaN; this._point = 0; }, lineEnd: function() { switch (this._point) { case 2: this._context.lineTo(this._x2, this._y2); break; case 3: point$2(this, this._x1, this._y1); break; } if (this._line || (this._line !== 0 && this._point === 1)) this._context.closePath(); this._line = 1 - this._line; }, point: function(x, y) { x = +x, y = +y; switch (this._point) { case 0: this._point = 1; this._line ? this._context.lineTo(x, y) : this._context.moveTo(x, y); break; case 1: this._point = 2; this._x1 = x, this._y1 = y; break; case 2: this._point = 3; // proceed default: point$2(this, x, y); break; } this._x0 = this._x1, this._x1 = this._x2, this._x2 = x; this._y0 = this._y1, this._y1 = this._y2, this._y2 = y; } }; const curveCardinal = (function custom(tension) { function cardinal(context) { return new Cardinal(context, tension); } cardinal.tension = function(tension) { return custom(+tension); }; return cardinal; })(0); function point$1(that, x, y) { var x1 = that._x1, y1 = that._y1, x2 = that._x2, y2 = that._y2; if (that._l01_a > math.epsilon) { var a = 2 * that._l01_2a + 3 * that._l01_a * that._l12_a + that._l12_2a, n = 3 * that._l01_a * (that._l01_a + that._l12_a); x1 = (x1 * a - that._x0 * that._l12_2a + that._x2 * that._l01_2a) / n; y1 = (y1 * a - that._y0 * that._l12_2a + that._y2 * that._l01_2a) / n; } if (that._l23_a > math.epsilon) { var b = 2 * that._l23_2a + 3 * that._l23_a * that._l12_a + that._l12_2a, m = 3 * that._l23_a * (that._l23_a + that._l12_a); x2 = (x2 * b + that._x1 * that._l23_2a - x * that._l12_2a) / m; y2 = (y2 * b + that._y1 * that._l23_2a - y * that._l12_2a) / m; } that._context.bezierCurveTo(x1, y1, x2, y2, that._x2, that._y2); } function CatmullRom(context, alpha) { this._context = context; this._alpha = alpha; } CatmullRom.prototype = { areaStart: function() { this._line = 0; }, areaEnd: function() { this._line = NaN; }, lineStart: function() { this._x0 = this._x1 = this._x2 = this._y0 = this._y1 = this._y2 = NaN; this._l01_a = this._l12_a = this._l23_a = this._l01_2a = this._l12_2a = this._l23_2a = this._point = 0; }, lineEnd: function() { switch (this._point) { case 2: this._context.lineTo(this._x2, this._y2); break; case 3: this.point(this._x2, this._y2); break; } if (this._line || (this._line !== 0 && this._point === 1)) this._context.closePath(); this._line = 1 - this._line; }, point: function(x, y) { x = +x, y = +y; if (this._point) { var x23 = this._x2 - x, y23 = this._y2 - y; this._l23_a = Math.sqrt(this._l23_2a = Math.pow(x23 * x23 + y23 * y23, this._alpha)); } switch (this._point) { case 0: this._point = 1; this._line ? this._context.lineTo(x, y) : this._context.moveTo(x, y); break; case 1: this._point = 2; break; case 2: this._point = 3; // proceed default: point$1(this, x, y); break; } this._l01_a = this._l12_a, this._l12_a = this._l23_a; this._l01_2a = this._l12_2a, this._l12_2a = this._l23_2a; this._x0 = this._x1, this._x1 = this._x2, this._x2 = x; this._y0 = this._y1, this._y1 = this._y2, this._y2 = y; } }; const curveCatmullRom = (function custom(alpha) { function catmullRom(context) { return alpha ? new CatmullRom(context, alpha) : new Cardinal(context, 0); } catmullRom.alpha = function(alpha) { return custom(+alpha); }; return catmullRom; })(0.5); function sign(x) { return x < 0 ? -1 : 1; } // Calculate the slopes of the tangents (Hermite-type interpolation) based on // the following paper: Steffen, M. 1990. A Simple Method for Monotonic // Interpolation in One Dimension. Astronomy and Astrophysics, Vol. 239, NO. // NOV(II), P. 443, 1990. function slope3(that, x2, y2) { var h0 = that._x1 - that._x0, h1 = x2 - that._x1, s0 = (that._y1 - that._y0) / (h0 || h1 < 0 && -0), s1 = (y2 - that._y1) / (h1 || h0 < 0 && -0), p = (s0 * h1 + s1 * h0) / (h0 + h1); return (sign(s0) + sign(s1)) * Math.min(Math.abs(s0), Math.abs(s1), 0.5 * Math.abs(p)) || 0; } // Calculate a one-sided slope. function slope2(that, t) { var h = that._x1 - that._x0; return h ? (3 * (that._y1 - that._y0) / h - t) / 2 : t; } // According to https://en.wikipedia.org/wiki/Cubic_Hermite_spline#Representations // "you can express cubic Hermite interpolation in terms of cubic Bézier curves // with respect to the four values p0, p0 + m0 / 3, p1 - m1 / 3, p1". function point(that, t0, t1) { var x0 = that._x0, y0 = that._y0, x1 = that._x1, y1 = that._y1, dx = (x1 - x0) / 3; that._context.bezierCurveTo(x0 + dx, y0 + dx * t0, x1 - dx, y1 - dx * t1, x1, y1); } function MonotoneX(context) { this._context = context; } MonotoneX.prototype = { areaStart: function() { this._line = 0; }, areaEnd: function() { this._line = NaN; }, lineStart: function() { this._x0 = this._x1 = this._y0 = this._y1 = this._t0 = NaN; this._point = 0; }, lineEnd: function() { switch (this._point) { case 2: this._context.lineTo(this._x1, this._y1); break; case 3: point(this, this._t0, slope2(this, this._t0)); break; } if (this._line || (this._line !== 0 && this._point === 1)) this._context.closePath(); this._line = 1 - this._line; }, point: function(x, y) { var t1 = NaN; x = +x, y = +y; if (x === this._x1 && y === this._y1) return; // Ignore coincident points. switch (this._point) { case 0: this._point = 1; this._line ? this._context.lineTo(x, y) : this._context.moveTo(x, y); break; case 1: this._point = 2; break; case 2: this._point = 3; point(this, slope2(this, t1 = slope3(this, x, y)), t1); break; default: point(this, this._t0, t1 = slope3(this, x, y)); break; } this._x0 = this._x1, this._x1 = x; this._y0 = this._y1, this._y1 = y; this._t0 = t1; } }; (Object.create(MonotoneX.prototype)).point = function(x, y) { MonotoneX.prototype.point.call(this, y, x); }; function monotoneX(context) { return new MonotoneX(context); } function Natural(context) { this._context = context; } Natural.prototype = { areaStart: function() { this._line = 0; }, areaEnd: function() { this._line = NaN; }, lineStart: function() { this._x = []; this._y = []; }, lineEnd: function() { var x = this._x, y = this._y, n = x.length; if (n) { this._line ? this._context.lineTo(x[0], y[0]) : this._context.moveTo(x[0], y[0]); if (n === 2) { this._context.lineTo(x[1], y[1]); } else { var px = controlPoints(x), py = controlPoints(y); for (var i0 = 0, i1 = 1; i1 < n; ++i0, ++i1) { this._context.bezierCurveTo(px[0][i0], py[0][i0], px[1][i0], py[1][i0], x[i1], y[i1]); } } } if (this._line || (this._line !== 0 && n === 1)) this._context.closePath(); this._line = 1 - this._line; this._x = this._y = null; }, point: function(x, y) { this._x.push(+x); this._y.push(+y); } }; // See https://www.particleincell.com/2012/bezier-splines/ for derivation. function controlPoints(x) { var i, n = x.length - 1, m, a = new Array(n), b = new Array(n), r = new Array(n); a[0] = 0, b[0] = 2, r[0] = x[0] + 2 * x[1]; for (i = 1; i < n - 1; ++i) a[i] = 1, b[i] = 4, r[i] = 4 * x[i] + 2 * x[i + 1]; a[n - 1] = 2, b[n - 1] = 7, r[n - 1] = 8 * x[n - 1] + x[n]; for (i = 1; i < n; ++i) m = a[i] / b[i - 1], b[i] -= m, r[i] -= m * r[i - 1]; a[n - 1] = r[n - 1] / b[n - 1]; for (i = n - 2; i >= 0; --i) a[i] = (r[i] - a[i + 1]) / b[i]; b[n - 1] = (x[n] + a[n - 1]) / 2; for (i = 0; i < n - 1; ++i) b[i] = 2 * x[i + 1] - a[i + 1]; return [a, b]; } function curveNatural(context) { return new Natural(context); } function Step(context, t) { this._context = context; this._t = t; } Step.prototype = { areaStart: function() { this._line = 0; }, areaEnd: function() { this._line = NaN; }, lineStart: function() { this._x = this._y = NaN; this._point = 0; }, lineEnd: function() { if (0 < this._t && this._t < 1 && this._point === 2) this._context.lineTo(this._x, this._y); if (this._line || (this._line !== 0 && this._point === 1)) this._context.closePath(); if (this._line >= 0) this._t = 1 - this._t, this._line = 1 - this._line; }, point: function(x, y) { x = +x, y = +y; switch (this._point) { case 0: this._point = 1; this._line ? this._context.lineTo(x, y) : this._context.moveTo(x, y); break; case 1: this._point = 2; // proceed default: { if (this._t <= 0) { this._context.lineTo(this._x, y); this._context.lineTo(x, y); } else { var x1 = this._x * (1 - this._t) + x * this._t; this._context.lineTo(x1, this._y); this._context.lineTo(x1, y); } break; } } this._x = x, this._y = y; } }; function curveStep(context) { return new Step(context, 0.5); } function stepBefore(context) { return new Step(context, 0); } function stepAfter(context) { return new Step(context, 1); } class LineChartBandScaleSpecifics { xScale(data, width) { return groupConfigUtils.band() .domain(this.xAxisEntries(data)) .range([0, width]) .padding(0.2); } xAxisEntries(data) { return Object.keys(data); } mapValues(groupName, data) { return (key) => ({ key: key, value: +data[key][groupName] }); } /** Check if the max label width is too wide. * For band scales we can simply make use of bandwidth() */ isXLabelTooWide(xScale, maxXTickLabelWidth) { { const bandScale = xScale; return maxXTickLabelWidth > bandScale.bandwidth() * 1.1; } } closestDataPointFnWith(data, xScale, offsetX, selectedGroup) { return (mx) => { const key = LineChartBandScaleSpecifics.scaleBandInvert(xScale, offsetX)(mx); const values = selectedGroup && selectedGroup in data[key] ? { [selectedGroup]: data[key][selectedGroup] } : data[key]; return { key, values, }; }; } xOffset(xScale) { return xScale.bandwidth() / 2; } xPositionForKey(xScale) { return (key) => xScale(key) + this.xOffset(xScale); } static scaleBandInvert(scale, xOffset) { const domain = scale.domain(); const paddingOuter = scale(domain[0]); const eachBand = scale.step(); return function (value) { const index = Math.floor((value - paddingOuter - xOffset) / eachBand); return domain[Math.max(0, Math.min(index, domain.length - 1))]; }; } } class LineChartSvgDataGroup { constructor(selection) { this.selection = selection; this.curveFunctions = { curveLinear: curveLinear, curveStep: curveStep, curveStepBefore: stepBefore, curveStepAfter: stepAfter, curveBasis: curveBasis, curveBundle: curveBundle, curveCardinal: curveCardinal, curveMonotoneX: monotoneX, curveNatural: curveNatural, curveCatmullRom: curveCatmullRom, }; } updateLines(xPositionForKeyFn, yScale, curveFnName, transitionDurationMs, eventEmitter) { const lines = line() .x((d) => xPositionForKeyFn(d.key)) .y((d) => yScale(+d.value)) .curve(this.curveFunctions[curveFnName] || curveLinear); this.selection .selectAll(".uic-line-chart__line") .data((d) => [d]) .join((enter) => enter .append("path") .style("fill", "none") .attr("clip-path", "url(#clip)") .attr("class", "uic-line-chart__line") .attr("d", (d) => lines(d.values)) .on("click touchstart", (e, d) => { e.preventDefault(); e.stopPropagation(); eventEmitter.emit(d); }), (update) => update .transition() .duration(transitionDurationMs) .attr("d", (d) => lines(d.values)) .selection()); return this; } updateDots(xPositionForKeyFn, yScale, transitionDurationMs, hideDots, eventEmitter) { this.selection .selectAll(".uic-line-chart__dots") .data(function (d) { if (hideDots) return []; const strokeWidth = getComputedStyle(this).strokeWidth; const dotSize = strokeWidth.endsWith("px") ? parseInt(strokeWidth.slice(0, strokeWidth.length - 2)) * 3 : 9; // 3 times the default stroke width, just as fallback return d.values.map((v) => (Object.assign(Object.assign({}, d), { value: v, dotSize }))); }) .join((enter) => enter .append("rect") .attr("class", "uic-line-chart__dots") .call((d) => LineChartSvgDataGroup.positionDots(d, xPositionForKeyFn, yScale)) .on("click touchstart", (e, d) => { e.preventDefault(); e.stopPropagation(); eventEmitter.emit(d); }), (update) => update .transition() .duration(transitionDurationMs) .call((d) => LineChartSvgDataGroup.positionDots(d, xPositionForKeyFn, yScale)) .selection()); return this; } static positionDots(selection, xPositionForKeyFn, yScale) { selection .attr("width", ({ dotSize }) => dotSize) .attr("height", ({ dotSize }) => dotSize) .attr("x", ({ value: d, dotSize }) => xPositionForKeyFn(d.key) - dotSize / 2) .attr("y", ({ value: d, dotSize }) => yScale(+d.value) - dotSize / 2); } } class LineChartSvgChartArea { constructor(svg, margin) { this.selection = svg .append("g") .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); } getXAxisArea() { return this.xAxisArea; } getYAxisArea() { return this.yAxisArea; } appendYAxis() { this.yAxisArea = this.selection .append("g") .attr("class", "uic-line-chart__y-axis"); return this.yAxisArea; } appendXAxis(parentHeight, margin) { this.xAxisArea = this.selection .append("g") .attr("class", "uic-line-chart__x-axis") .attr("transform", `translate(0,${parentHeight - margin.bottom - margin.top})`); return this.xAxisArea; } appendDataGroupArea() { this.selection.append("g").attr("class", "uic-line-chart"); } updateChartArea(margin, yAxisLabelOffset, maxYTickLabelWidth) { this.selection.attr("transform", `translate(${margin.left + yAxisLabelOffset + maxYTickLabelWidth}, ${margin.top})`); return this; } updateYAxis(yScale, yTicks, yFormatter, xAxisWidth, transitionDurationMs) { this.selection .select(".uic-line-chart__y-axis") .transition() .duration(transitionDurationMs) .call(LineChartSvgChartArea.yAxis(yScale, yTicks, yFormatter, xAxisWidth)); return this; } updateXAxis(xScale, parentHeight, rotateXAxisLabels, labelRotationDegrees, additionalXTickLabelSpace, margin, xFormatter, transitionDurationMs) { const xAxis = this.selection.select(".uic-line-chart__x-axis"); xAxis .transition() .duration(transitionDurationMs) .attr("transform", `translate(0, ${parentHeight - margin.bottom - margin.top - additionalXTickLabelSpace})`) .call(LineChartSvgChartArea.xAxis(xScale, xFormatter)); // x axis label direction const text = xAxis.selectAll("text"); if (rotateXAxisLabels) { // instead of shrinking the chart we may crop the labels if they get too long, maybe use textPath with diagonal line instead as it crops the text automatically // see https://stackoverflow.com/questions/9241315/trimming-text-to-a-given-pixel-width-in-svg, https://bl.ocks.org/armollica/90ff1d55f7a8fdbf42500558e1c6d1f5 text.style("text-anchor", "end") .attr("dx", "-.8em") .attr("dy", ".15em") .attr("transform", `rotate(-${labelRotationDegrees})`); } else { text.style("text-anchor", "inherit") .attr("dy", "0.71em") .attr("dx", null) .attr("transform", null); } return this; } updateDataGroups(data) { const dataGroups = this.selection .select(".uic-line-chart") .selectAll(".uic-line-chart__data-group") .data(data) .join((enter) => enter.append("g")) .attr("class", (d) => `${d.cssClass} uic-line-chart__data-group`) .classed("uic-line-chart__data-group--ignore", (d) => d.ignore); return new LineChartSvgDataGroup(dataGroups); } static xAxis(xScale, xFromatter) { return groupConfigUtils.axisBottom(xScale) .tickSizeOuter(0) .tickFormat((val) => groupConfigUtils.transform(xFromatter, val)); } static yAxis(yScale, yTicks, yFormatter, width) { return groupConfigUtils.axisLeft(yScale) .ticks(yTicks) .tickFormat((val) => groupConfigUtils.transform(yFormatter, val).toString()) .tickSize(-width); } } class LineChartSvg { constructor(id, parentWidth, parentHeight) { this.selection = groupConfigUtils.select(`#${id}`) .append("svg") .attr("width", parentWidth) .attr("height", parentHeight); } show() { this.selection.style("visibility", "inherit"); return this; } hide() { this.selection.style("visibility", "hidden"); return this; } appendYAxisLabelArea() { return this.selection .append("g") .attr("class", "uic-line-chart__y-axis-label"); } appendChartArea(margin) { this.chartArea = new LineChartSvgChartArea(this.selection, margin); return this.chartArea; } updateDimensions(parentWidth, parentHeight, transitionDurationMs) { this.selection .transition() .duration(transitionDurationMs) .attr("width", parentWidth) .attr("height", parentHeight); return this; } updateYAxisLabel(yAxisLabelData, margin, parentHeight, transitionDurationMs) { const yAxisLabel = this.selection .select(".uic-line-chart__y-axis-label") .selectAll("text") .data(yAxisLabelData); yAxisLabel .enter() .append("text") .attr("text-anchor", "middle") .attr("y", margin.left) .attr("x", -parentHeight / 2) .attr("transform", "rotate(-90)") .attr("dy", "1em") .text((d) => d); yAxisLabel .text((d) => d) .transition() .duration(transitionDurationMs) .attr("y", margin.left) .attr("x", -parentHeight / 2); yAxisLabel.exit().remove(); return this; } addTooltipHandler(tooltipElement, bisect, xScale, yScale, xOffset, yOffset, tooltipTextFormatter) { if (!tooltipElement) { this.selection.on("touchmove mousemove touchstart", null); this.selection.on("touchend mouseleave", null); return this; } const tooltipLine = this.selection .selectAll(".uic-line-chart__tooltip-line") .data([null]) // only ever append one line .join("line") .classed("uic-line-chart__tooltip-line", true) .attr("display", "none"); groupConfigUtils.select(tooltipElement).on("touchend mouseleave", function () { tooltipElement.open = false; tooltipLine.style("display", "none"); }); this.selection.on("touchmove mousemove touchstart", function (event) { event.preventDefault(); // reopen to trigger popperjs placement tooltipElement.open = false; tooltipElement.open = true; const pointerEvent = window.TouchEvent && event instanceof TouchEvent ? event.touches[0] : event; const dataPoint = bisect(pointer(pointerEvent, this)[0]); const y = Math.max(...Object.values(dataPoint.values)); const x = xScale(dataPoint.key) + xOffset; groupConfigUtils.select(tooltipElement) .style("position", "absolute") .style("display", "block") .style("left", x + "px") .style("top", yScale(y) + yOffset + "px") .select('[slot="tip"]') .html(groupConfigUtils.transform(tooltipTextFormatter, dataPoint)); tooltipLine .style("display", "initial") .attr("x1", x + "px") .attr("x2", x + "px") .attr("y1", yScale(0) + yOffset) .attr("y2", yScale(y) + yOffset); }); this.selection.on("touchend mouseleave", function (event) { if (event.relatedTarget && event.relatedTarget.closest("uic-tooltip")) { return; } tooltipElement.open = false; tooltipLine.style("display", "none"); }); return this; } } class LineChartTimeScaleSpecifics { xScale(data, width) { const dates = this.xAxisEntries(data); return time() .domain([dates[0], dates[dates.length - 1]]) .range([0, width]) .nice(); } xAxisEntries(data) { return Object.keys(data).map((x) => new Date(x)); } mapValues(groupName, data) { return (key) => ({ key: new Date(key), value: +data[key][groupName] }); } /** Check if the max label width is too wide. * For time scales we have to check against the distance of consecutive ticks because there is not bandwidth() */ isXLabelTooWide(xScale, maxXTickLabelWidth, xAxisTicks) { const timeScale = xScale; const { rotate } = xAxisTicks .map((d) => new Date(d)) .reduce((agg, d) => { return { prev: d, rotate: maxXTickLabelWidth > timeScale(d) - timeScale(agg.prev), }; }, { prev: new Date(0), rotate: false }); return rotate; } closestDataPointFnWith(data, xScale, offsetX, selectedGroup) { const bisectFn = groupConfigUtils.bisector((d) => d.key).left; return (mx) => { const dataArray = Object.entries(data).map((r) => ({ key: new Date(r[0]), values: r[1], })); const key = xScale.invert(mx - offsetX); const index = bisectFn(dataArray, key, 1); const a = dataArray[index - 1]; const b = dataArray[index]; const closestDataPoint = b && key.getTime() - a.key.getTime() > b.key.getTime() - key.getTime() ? b : a; const values = selectedGroup && selectedGroup in closestDataPoint.values ? { [selectedGroup]: closestDataPoint.values[selectedGroup], } : closestDataPoint.values; return { key: closestDataPoint.key, values, }; }; } xOffset() { return 0; } xPositionForKey(xScale) { return (key) => xScale(key); } } const lineChartCss = ".uic-line-chart{font-size:var(--uic-charts-line-chart-general-font-size, 0.8rem);display:flex;align-items:center;justify-content:center;position:relative;color:var(--uic-charts-line-chart-text-color, black)}.uic-line-chart__x-axis .tick line{display:none}.uic-line-chart__x-axis text{font-weight:var(--uic-charts-line-chart-x-axis-label-font-weight, bold);font-size:var(--uic-charts-line-chart-x-axis-label-font-size, 0.8rem)}.uic-line-chart__y-axis path{display:none}.uic-line-chart__y-axis .tick line{stroke:var(--uic-charts-line-chart-tick-line-color, #eee)}.uic-line-chart__y-axis text{font-weight:var(--uic-charts-line-chart-y-axis-tick-font-weight, normal);font-size:var(--uic-charts-line-chart-y-axis-tick-font-size, 0.8rem)}.uic-line-chart__y-axis-label{font-weight:var(--uic-charts-line-chart-y-axis-label-font-weight, normal);font-size:var(--uic-charts-line-chart-y-axis-label-font-size, 0.8rem)}.uic-line-chart__data-group{stroke-width:var(--uic-charts-line-chart-stroke-width, 3px);stroke:var(--uic-charts-data-group-color, black)}.uic-line-chart__data-group.uic-line-chart__data-group--ignore{opacity:var(--uic-charts-line-chart-fade-out-opacity);stroke-width:var(--uic-charts-line-chart-fade-out-stroke-width, var(--uic-charts-line-chart-stroke-width, 3px));stroke:var(--uic-charts-line-chart-fade-out-stroke-color, var(--uic-charts-data-group-color, black))}.uic-line-chart__dots{fill:var(--uic-charts-line-chart-dot-fill-color, white)}.uic-line-chart__tooltip-line{stroke:var(--uic-tooltip-border-color);stroke-dasharray:var(--uic-charts-line-chart-tooltip-line-stroke-dasharray, 4);box-shadow:0 0 6px 0 var(--uic-color-blue6)}.uic-line-chart__tooltip-color-indicator{width:1em;height:1em;margin:0 0.5em 0 0;display:inline-block;vertical-align:middle;background-color:var(--uic-charts-data-group-color, black)}.uic-line-chart__loading_indicator{position:absolute;display:flex;align-items:center;justify-content:center;width:100%;height:100%;text-align:center;vertical-align:middle;background:var(--uic-charts-line-chart-loading-overlay-color, white)}.uic-line-chart uic-loader{position:absolute;display:flex;align-items:center;justify-content:center;width:max(188px, 25%);text-align:center;vertical-align:middle}.uic-line-chart__no-data-indicator{position:absolute;display:flex;align-items:center;justify-content:center;width:100%;height:100%}"; let LineChart = class { constructor(hostRef) { index.registerInstance(this, hostRef); this.lineSelect = index.createEvent(this, "uicLineSelect", 7); this.lineSelectInternal = index.createEvent(this, "uicLineSelectInternal", 7); /** Select the x axis scale to use * Time scales always keep a continuous timeline even if time values are skipped in the input data. * Band scales allow for skipped values even if using a time measure for the x axis. * You need to supply the `data` group keys as strings. It's best to use the standard iso format of yyyy-MM-dd'T'HH-mm-ss'Z' when using the time scale. */ this.xScaleType = "time"; /** Choose one of d3's curve interpolation functions. * The default ist curveLinear (= no interpolation) * see e.g. http://bl.ocks.org/d3indepth/b6d4845973089bc1012dec1674d3aff8 for a quick comparison */ this.curveFunctionName = "curveLinear"; /** Whether the datapoint dots shouldn't be displayed */ this.hideDots = false; /** Number of ticks (entries) on the Y axis. * In some cases d3 overwrites this value and chooses the best automatically, * depending on the current width and height of the chart. */ this.yTicks = 6; /** Use this flag if you don't want to use the tooltips */ this.disableTooltip = false; /** Tooltip text formatter. Gets the x axis key and values for all data points at that location as input. * There's a default formatter which sorts by value, uses x and y formatters and adds a color dot per group by setting the group css class */ this.tooltipFormatter = this.defaultTooltipFormatter.bind(this); /** Customize the margin. You don't have to change them, labels won't be truncated regardless of these values */ this.margin = { top: 20, right: 20, bottom: 20, left: 20 }; /** Can be used to define a custom line chart root class for styling purposes.*/ this.lineChartRootClass = ""; /** State in which the line chart is currently in. */ this.state = chartState.ChartState.SUCCESS; /** Text for displaying when no data is available. */ this.noDataText = "No data available"; /** Set the transition duration. As long as the transitions do not cause any issues, keep the default */ this.transitionDurationMs = 500; /** id attribute for this chart. A unique id will be generated if this is empty */ this.chartId = ""; /** Set this prop to one of your group keys (= lines) to highlight it */ this.selectedGroup = undefined; this.debouncedUpdateSvg = debounce.debounce(() => this.updateSvg(), 100); this.yAxisLabelOffset = 30; this.xAxisLabelRotationDegrees = 65; if (this.xScaleType === "time") { // Set reasonable default formatter for dates this.xFormatter = LineChart.defaultXDateFormatter; this.scaleSpecifics = new LineChartTimeScaleSpecifics(); } else { this.scaleSpecifics = new LineChartBandScaleSpecifics(); } } onResize() { this.debouncedUpdateSvg(); } onLineSelect(e) { const lineSelectEvent = this.lineSelect.emit(e.detail); if (lineSelectEvent.defaultPrevented) { return; } if (!e.detail.name || this.selectedGroup === e.detail.name) { this.selectedGroup = undefined; } else { this.selectedGroup = e.detail.name; } } onClick() { this.selectedGroup = undefined; } componentDidLoad() { this.initSvg(); } componentWillLoad() { const { chartId } = this; this.chartId = chartId || uniqueId.getUniqueId(); } curveFunctionChanged() { this.updateChart(); } xScaleTypeChanged(val) { if (val === "time") { this.scaleSpecifics = new LineChartTimeScaleSpecifics(); } else { this.scaleSpecifics = new LineChartBandScaleSpecifics(); } } componentDidRender() { this.updateSvg(); } initSvg() { const { width: parentWidth, height: parentHeight, } = this.el.parentElement.getBoundingClientRect(); this.svg = new LineChartSvg(this.chartId, parentWidth, parentHeight); const chartArea = this.svg.appendChartArea(this.margin); chartArea.appendYAxis(); chartArea.appendXAxis(parentHeight, this.margin); chartArea.appendDataGroupArea(); this.svg.appendYAxisLabelArea(); this.updateSvg(); } updateSvg() { if (this.svg === undefined) return; if (this.state !== chartState.ChartState.NODATA && this.data !== undefined && this.data !== null && Object.keys(this.data).length > 0) { this.svg.show(); this.updateChart(); } else { this.svg.hide(); } } updateChart() { const { width: parentWidth, height: parentHeight, } = this.el.parentElement.getBoundingClientRect(); const yAxisLabelOffset = LineChart.hasYAxisLabel(this.yAxisLabel) ? this.yAxisLabelOffset : 0; const yAxisLabelData = LineChart.hasYAxisLabel(this.yAxisLabel) ? [this.yAxisLabel] : []; const [minYValue, maxYValue] = LineChart.yValueLimits(this.data, this.yMinValue, this.yMaxValue); const maxYTickLabelWidth = LineChart.maxYTickLabelWidth(this.svg.chartArea.getYAxisArea(), maxYValue, this.yFormatter); const xAxisWidth = LineChart.xScaleWidth(parentWidth, this.margin, maxYTickLabelWidth, yAxisLabelOffset); const xScale = this.scaleSpecifics.xScale(this.data, xAxisWidth); const xAxisEntries = this.scaleSpecifics.xAxisEntries(this.data); const maxXTickLabelWidth = LineChart.maxXTickLabelWidth(this.svg.chartArea.getXAxisArea(), xAxisEntries, this.xFormatter); const shouldRotateXAxisLabels = this.scaleSpecifics.isXLabelTooWide(xScale, maxXTickLabelWidth, xAxisEntries); const additionalXTickLabelSpace = shouldRotateXAxisLabels ? maxXTickLabelWidth * Math.sin(this.xAxisLabelRotationDegrees * (Math.PI / 180)) : 0; const yScale = LineChart.yScale(parentHeight - this.margin.bottom - this.margin.top - additionalXTickLabelSpace, maxYValue, minYValue); const closestDataPointFn = this.scaleSpecifics.closestDataPointFnWith(this.data, xScale, this.margin.left + maxYTickLabelWidth + yAxisLabelOffset, this.selectedGroup); const xPositionForKeyFn = this.scaleSpecifics.xPositionForKey(xScale); this.svg .updateDimensions(parentWidth, parentHeight, this.transitionDurationMs) .updateYAxisLabel(yAxisLabelData, this.margin, parentHeight, this.transitionDurationMs) .addTooltipHandler(this.tooltip, closestDataPointFn, xScale, yScale, this.margin.left + yAxisLabelOffset + maxYTickLabelWidth + this.scaleSpecifics.xOffset(xScale), this.margin.top, this.tooltipFormatter) .chartArea.updateChartArea(this.margin, yAxisLabelOffset, maxYTickLabelWidth) .updateYAxis(yScale, this.yTicks, this.yFormatter, xAxisWidth, this.transitionDurationMs) .updateXAxis(xScale, parentHeight, shouldRotateXAxisLabels, this.xAxisLabelRotationDegrees, additionalXTickLabelSpace, this.margin, this.xFormatter, this.transitionDurationMs) .updateDataGroups(LineChart.prepareData(this.data, this.groupConfig, this.scaleSpecifics.mapValues, this.selectedGroup)) .updateLines(xPositionForKeyFn, yScale, this.curveFunctionName, this.transitionDurationMs, this.lineSelectInternal) .updateDots(xPositionForKeyFn, yScale, this.transitionDurationMs, this.hideDots, this.lineSelectInternal); } /** Default tooltip formatter, sorts by value, uses x and y formatters and adds a color dot per group by setting the group css class */ defaultTooltipFormatter({ key, values, }) { const formattedXKey = groupConfigUtils.transform(this.xFormatter, key); const dataGroupsAndValues = Object.entries(values) .sort((a, b) => (a[1] < b[1] ? 1 : b[1] <= a[1] ? -1 : 0)) .reduce((s, e) => { const cssClass = groupConfigUtils.getCssClassForKey(this.groupConfig, e[0]); const dataGroup = groupConfigUtils.getLabelForKey(this.groupConfig, e[0]); const formattedYValue = groupConfigUtils.transform(this.yFormatter, e[1]); return (s + `