@flexem/fc-gui 3.0.0-alpha.158 → 3.0.0-alpha.159

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -22355,6 +22355,9 @@ const Localization_zh_CN = {
22355
22355
  lastSevenDays: '最近7天',
22356
22356
  lastThirtyDays: '最近30天',
22357
22357
  lastOneYear: '最近1年',
22358
+ customTimeRange: '自定义',
22359
+ startTime: '开始时间',
22360
+ endTime: '结束时间',
22358
22361
  grouped: '分组',
22359
22362
  stacked: '叠加',
22360
22363
  passwordVerify: '密码校验',
@@ -32551,6 +32554,9 @@ const DefaultLocalization = {
32551
32554
  lastSevenDays: 'Last 7 days',
32552
32555
  lastThirtyDays: 'Last 30 days',
32553
32556
  lastOneYear: 'Last 1 year',
32557
+ customTimeRange: 'Custom',
32558
+ startTime: 'Start Time',
32559
+ endTime: 'End Time',
32554
32560
  grouped: 'Grouped',
32555
32561
  stacked: 'Stacked',
32556
32562
  passwordVerify: 'Password verifiers',
@@ -37173,9 +37179,13 @@ class historical_curve_element_HistoricalCurveElement extends conditional_displa
37173
37179
  operationButtonHeight: 24,
37174
37180
  operationButtonMargin: 4
37175
37181
  };
37182
+ this.customStartTime = null;
37183
+ this.customEndTime = null;
37176
37184
  this.elementStatus = HistoricalCurveElementStatus.Loading;
37177
37185
  this.data = [];
37178
37186
  this.needResize = true;
37187
+ this.dropdownListEl = null;
37188
+ this.dropdownTriggerEl = null;
37179
37189
  this.setNeedResize = () => {
37180
37190
  this.needResize = false;
37181
37191
  setTimeout(() => this.needResize = true, 500);
@@ -37259,21 +37269,43 @@ class historical_curve_element_HistoricalCurveElement extends conditional_displa
37259
37269
  * 更新语种相关的文案(时间段选择器)
37260
37270
  */
37261
37271
  updateLanguageTexts() {
37262
- // 重新生成时间段数据
37263
37272
  const updatedTimePeriods = this.getValidTimePeriods();
37264
37273
  this.timePeriods = updatedTimePeriods;
37265
- // select 在 foreignObject 内部的 HTML 命名空间中,D3 的 rootElement.select('select') 无法跨命名空间查找
37266
- // 改用 jQuery 查找
37267
- const nativeSelectEl = this.$element && this.$element.find('select');
37268
- if (nativeSelectEl && nativeSelectEl.length > 0) {
37269
- const options = nativeSelectEl.find('option');
37270
- options.each(function (i) {
37274
+ // 更新下拉列表各选项文字
37275
+ if (this.dropdownListEl) {
37276
+ const items = this.dropdownListEl.querySelectorAll('.hc-dropdown-item');
37277
+ items.forEach((item, i) => {
37271
37278
  if (i < updatedTimePeriods.length) {
37272
- this.text = updatedTimePeriods[i].name;
37279
+ item.textContent = updatedTimePeriods[i].name;
37273
37280
  }
37274
37281
  });
37282
+ // 文字更新后重新测量宽度,修正首次加载时文字为空导致宽度过小的问题
37283
+ const prevDisplay = this.dropdownListEl.style.display;
37284
+ const prevVisibility = this.dropdownListEl.style.visibility;
37285
+ this.dropdownListEl.style.display = 'block';
37286
+ this.dropdownListEl.style.visibility = 'hidden';
37287
+ this.dropdownListEl.style.width = '';
37288
+ const newWidth = this.dropdownListEl.offsetWidth;
37289
+ this.dropdownListEl.style.display = prevDisplay;
37290
+ this.dropdownListEl.style.visibility = prevVisibility;
37291
+ this.dropdownListEl.style.width = newWidth + 'px';
37292
+ if (this.dropdownTriggerEl) {
37293
+ this.dropdownTriggerEl.style.width = newWidth + 'px';
37294
+ }
37295
+ }
37296
+ // 更新触发按钮文字(显示当前选中项的最新语言文案)
37297
+ if (this.dropdownTriggerEl) {
37298
+ const selected = updatedTimePeriods.find(p => p.key === Number(this.currentTimePeriod));
37299
+ if (selected) {
37300
+ const textNode = Array.from(this.dropdownTriggerEl.childNodes).find(n => n.nodeType === Node.TEXT_NODE);
37301
+ if (textNode) {
37302
+ textNode.textContent = selected.name;
37303
+ }
37304
+ else {
37305
+ this.dropdownTriggerEl.insertBefore(document.createTextNode(selected.name), this.dropdownTriggerEl.firstChild);
37306
+ }
37307
+ }
37275
37308
  }
37276
- // 如果 select 元素不存在,timePeriods 已更新,下次 renderOperationArea 时会使用新的文本
37277
37309
  }
37278
37310
  /**
37279
37311
  * 获取当前语种的 culture 代码
@@ -37309,12 +37341,13 @@ class historical_curve_element_HistoricalCurveElement extends conditional_displa
37309
37341
  timePeriods.push({ key: 3, name: this.getTimePeriodText(service["TIME_PERIOD_KEYS"].LAST_SEVEN_DAYS) });
37310
37342
  timePeriods.push({ key: 4, name: this.getTimePeriodText(service["TIME_PERIOD_KEYS"].LAST_THIRTY_DAYS) });
37311
37343
  timePeriods.push({ key: 5, name: this.getTimePeriodText(service["TIME_PERIOD_KEYS"].LAST_ONE_YEAR) });
37344
+ timePeriods.push({ key: 8, name: this.getTimePeriodText(service["TIME_PERIOD_KEYS"].CUSTOM, this.localization.customTimeRange) });
37312
37345
  return timePeriods;
37313
37346
  }
37314
37347
  /**
37315
- * 获取时间段文案(从系统文本库获取多语种翻译)
37348
+ * 获取时间段文案(从系统文本库获取多语种翻译,fallback 到 localization)
37316
37349
  */
37317
- getTimePeriodText(textKey) {
37350
+ getTimePeriodText(textKey, fallback = '') {
37318
37351
  const currentCulture = this.getCurrentCulture();
37319
37352
  const systemType = service["SYSTEM_TEXT_LIBRARY_TYPES"].COMPONENT_BUILTIN;
37320
37353
  if (this.systemTextLibraryService) {
@@ -37323,13 +37356,20 @@ class historical_curve_element_HistoricalCurveElement extends conditional_displa
37323
37356
  return translation;
37324
37357
  }
37325
37358
  }
37326
- return '';
37359
+ return fallback;
37327
37360
  }
37328
37361
  updateTimeRange(timePeriodType) {
37329
37362
  this.currentTimePeriod = +timePeriodType;
37330
37363
  this.updateQueryTimeRange();
37331
37364
  }
37332
37365
  updateQueryTimeRange() {
37366
+ if (this.currentTimePeriod === 8) {
37367
+ if (this.customStartTime && this.customEndTime) {
37368
+ this.startTime = this.customStartTime.clone();
37369
+ this.endTime = this.customEndTime.clone();
37370
+ }
37371
+ return;
37372
+ }
37333
37373
  this.endTime = moment();
37334
37374
  switch (this.currentTimePeriod) {
37335
37375
  case 1:
@@ -37704,18 +37744,143 @@ class historical_curve_element_HistoricalCurveElement extends conditional_displa
37704
37744
  const operationArea = this.rootElement.append('g').attr('transform', `translate(0,${chartHeight + this.displayOption.operationAreaMarginTop})`)
37705
37745
  .append('foreignObject').attr('width', chartWidth).attr('height', this.displayOption.operationAreaHeight).attr('fill', 'none')
37706
37746
  .append('xhtml:div').style('height', (this.displayOption.operationAreaHeight - 4) + 'px').style('overflow', 'hidden').style('margin-top', '4px');
37707
- const selectElement = operationArea.append('select').style('margin-left', this.displayOption.marginLeft + 'px')
37708
- .style('background-color', backgroundColor)
37709
- .style('font-size', this.displayOption.operationSelectFontSize).on('change', () => {
37710
- const displayTimePeriod = this.rootElement.select('select').property('value');
37711
- this.updateTimeRange(displayTimePeriod);
37712
- this.reRenderElement(this.startTime, this.endTime, this.displayOption.dataLimit, HistoricalCurveTimeRange.BeginOpenEndOpen);
37713
- });
37714
37747
  const rect = this.$element.parent().parent().find('rect');
37715
37748
  const fillColor = rect.attr('fill');
37716
- const options = selectElement.selectAll('option').data(this.timePeriods).enter().append('option');
37717
- options.text(d => d.name).attr('value', d => d.key).property('selected', d => d.key === Number(this.currentTimePeriod))
37718
- .style('background-color', this.model.displaySetting.axisSetting.filterBackgroudColor || fillColor);
37749
+ const dropdownBg = this.model.displaySetting.axisSetting.filterBackgroudColor || fillColor || '#fff';
37750
+ const dropdownWrapper = operationArea.append('div')
37751
+ .style('display', 'inline-block')
37752
+ .style('position', 'relative')
37753
+ .style('margin-left', this.displayOption.marginLeft + 'px')
37754
+ .style('vertical-align', 'middle');
37755
+ const selectedText = this.timePeriods.find(t => t.key === Number(this.currentTimePeriod));
37756
+ const dropdownTrigger = dropdownWrapper.append('div')
37757
+ .attr('class', 'hc-dropdown-trigger')
37758
+ .style('background-color', backgroundColor)
37759
+ .style('font-size', this.displayOption.operationSelectFontSize)
37760
+ .style('cursor', 'pointer')
37761
+ .style('padding', '0 18px 0 4px')
37762
+ .style('border', '1px solid #ccc')
37763
+ .style('border-radius', '2px')
37764
+ .style('position', 'relative')
37765
+ .style('white-space', 'nowrap')
37766
+ .style('line-height', (this.displayOption.operationAreaHeight - 8) + 'px')
37767
+ .style('min-height', (this.displayOption.operationAreaHeight - 8) + 'px')
37768
+ .text(selectedText ? selectedText.name : '');
37769
+ dropdownTrigger.append('span')
37770
+ .style('position', 'absolute')
37771
+ .style('right', '4px')
37772
+ .style('top', '50%')
37773
+ .style('transform', 'translateY(-50%)')
37774
+ .style('font-size', '10px')
37775
+ .style('pointer-events', 'none')
37776
+ .text('▼');
37777
+ const dropdownListEl = document.createElement('div');
37778
+ dropdownListEl.style.cssText = 'display:none;position:fixed;background:' + dropdownBg
37779
+ + ';border:1px solid #ccc;border-radius:2px;z-index:9999;box-shadow:0 2px 6px rgba(0,0,0,0.15);';
37780
+ document.body.appendChild(dropdownListEl);
37781
+ this.dropdownListEl = dropdownListEl;
37782
+ this.dropdownTriggerEl = dropdownTrigger.node();
37783
+ const dropdownList = d3["select"](dropdownListEl);
37784
+ this.timePeriods.forEach(tp => {
37785
+ dropdownList.append('div')
37786
+ .attr('class', 'hc-dropdown-item')
37787
+ .style('padding', '4px 8px')
37788
+ .style('cursor', 'pointer')
37789
+ .style('white-space', 'nowrap')
37790
+ .style('font-size', this.displayOption.operationSelectFontSize)
37791
+ .style('line-height', (this.displayOption.operationAreaHeight - 8) + 'px')
37792
+ .style('min-height', (this.displayOption.operationAreaHeight - 8) + 'px')
37793
+ .style('background', tp.key === Number(this.currentTimePeriod) ? '#1890ff' : 'transparent')
37794
+ .style('color', tp.key === Number(this.currentTimePeriod) ? '#fff' : '#333')
37795
+ .text(tp.name)
37796
+ .on('mouseover', function () {
37797
+ const el = d3["select"](this);
37798
+ if (!el.classed('hc-dropdown-selected')) {
37799
+ el.style('background', '#e6f7ff').style('color', '#333');
37800
+ }
37801
+ })
37802
+ .on('mouseout', function () {
37803
+ const el = d3["select"](this);
37804
+ if (!el.classed('hc-dropdown-selected')) {
37805
+ el.style('background', 'transparent').style('color', '#333');
37806
+ }
37807
+ })
37808
+ .on('click', () => {
37809
+ dropdownListEl.style.display = 'none';
37810
+ // 从 this.timePeriods 实时查找,确保语种切换后显示最新文案
37811
+ const currentPeriod = this.timePeriods.find(p => p.key === tp.key);
37812
+ const currentName = currentPeriod ? currentPeriod.name : tp.name;
37813
+ dropdownTrigger.text(currentName);
37814
+ dropdownTrigger.append('span')
37815
+ .style('position', 'absolute')
37816
+ .style('right', '4px')
37817
+ .style('top', '50%')
37818
+ .style('transform', 'translateY(-50%)')
37819
+ .style('font-size', '10px')
37820
+ .style('pointer-events', 'none')
37821
+ .text('▼');
37822
+ // 更新选中状态
37823
+ dropdownList.selectAll('.hc-dropdown-item')
37824
+ .classed('hc-dropdown-selected', false)
37825
+ .style('background', 'transparent')
37826
+ .style('color', '#333');
37827
+ d3["select"](d3["event"].currentTarget)
37828
+ .classed('hc-dropdown-selected', true)
37829
+ .style('background', '#1890ff')
37830
+ .style('color', '#fff');
37831
+ if (tp.key === 8) {
37832
+ this.showCustomTimeRangeModal();
37833
+ return;
37834
+ }
37835
+ this.updateTimeRange(tp.key);
37836
+ this.reRenderElement(this.startTime, this.endTime, this.displayOption.dataLimit, HistoricalCurveTimeRange.BeginOpenEndOpen);
37837
+ });
37838
+ });
37839
+ // 测量下拉列表最大宽度,让 trigger 和列表宽度一致
37840
+ dropdownListEl.style.display = 'block';
37841
+ dropdownListEl.style.visibility = 'hidden';
37842
+ const listContentWidth = dropdownListEl.offsetWidth;
37843
+ dropdownListEl.style.display = 'none';
37844
+ dropdownListEl.style.visibility = '';
37845
+ dropdownListEl.style.width = listContentWidth + 'px';
37846
+ dropdownTrigger.node().style.width = listContentWidth + 'px';
37847
+ dropdownTrigger.node().style.boxSizing = 'border-box';
37848
+ const hideDropdown = () => { dropdownListEl.style.display = 'none'; };
37849
+ dropdownTrigger.on('click', () => {
37850
+ const isVisible = dropdownListEl.style.display !== 'none';
37851
+ if (isVisible) {
37852
+ hideDropdown();
37853
+ }
37854
+ else {
37855
+ const triggerNode = dropdownTrigger.node();
37856
+ const triggerRect = triggerNode.getBoundingClientRect();
37857
+ // 设置宽度至少和 trigger 一样宽
37858
+ dropdownListEl.style.minWidth = triggerRect.width + 'px';
37859
+ dropdownListEl.style.left = triggerRect.left + 'px';
37860
+ dropdownListEl.style.display = 'block';
37861
+ const listHeight = dropdownListEl.offsetHeight;
37862
+ const spaceBelow = window.innerHeight - triggerRect.bottom;
37863
+ if (spaceBelow < listHeight) {
37864
+ // 向上展开
37865
+ dropdownListEl.style.top = '';
37866
+ dropdownListEl.style.bottom = (window.innerHeight - triggerRect.top) + 'px';
37867
+ }
37868
+ else {
37869
+ // 向下展开
37870
+ dropdownListEl.style.bottom = '';
37871
+ dropdownListEl.style.top = triggerRect.bottom + 'px';
37872
+ }
37873
+ }
37874
+ });
37875
+ // 点击外部收起
37876
+ document.addEventListener('click', (event) => {
37877
+ const triggerNode = dropdownTrigger.node();
37878
+ if (!dropdownListEl.contains(event.target) && !triggerNode.contains(event.target)) {
37879
+ hideDropdown();
37880
+ }
37881
+ });
37882
+ // 滚动时收起
37883
+ document.addEventListener('scroll', hideDropdown, true);
37719
37884
  const buttonWidth = this.displayOption.operationButtonWidth + 'px', buttonHeight = this.displayOption.operationButtonHeight + 'px';
37720
37885
  operationArea.append('button').style('width', buttonWidth).style('height', buttonHeight).style('border', 'none')
37721
37886
  .style('float', 'right').style('background-image', 'url(assets/img/black_last_page.png)')
@@ -37740,6 +37905,131 @@ class historical_curve_element_HistoricalCurveElement extends conditional_displa
37740
37905
  timeFormat(datetime, specifier) {
37741
37906
  return d3["time"].format(specifier)(new Date(datetime));
37742
37907
  }
37908
+ fmtDatetimeLocal(d) {
37909
+ const p2 = (n) => (n < 10 ? '0' : '') + n;
37910
+ return d.getFullYear() + '-' + p2(d.getMonth() + 1) + '-' + p2(d.getDate())
37911
+ + 'T' + p2(d.getHours()) + ':' + p2(d.getMinutes()) + ':' + p2(d.getSeconds());
37912
+ }
37913
+ showCustomTimeRangeModal() {
37914
+ const now = new Date();
37915
+ const defaultStart = this.customStartTime ? this.customStartTime.toDate()
37916
+ : new Date(now.getFullYear(), now.getMonth(), now.getDate(), 0, 0, 0);
37917
+ const defaultEnd = this.customEndTime ? this.customEndTime.toDate()
37918
+ : new Date(now.getFullYear(), now.getMonth(), now.getDate(), 23, 59, 59);
37919
+ if (this.guiContext && this.guiContext.showCustomTimeRangeModal) {
37920
+ this.guiContext.showCustomTimeRangeModal({ startTime: defaultStart, endTime: defaultEnd }).then(result => {
37921
+ if (result) {
37922
+ const momentAny = moment;
37923
+ this.customStartTime = momentAny(result.startTime);
37924
+ this.customEndTime = momentAny(result.endTime);
37925
+ this.updateTimeRange(8);
37926
+ this.reRenderElement(this.startTime, this.endTime, this.displayOption.dataLimit, HistoricalCurveTimeRange.BeginOpenEndOpen);
37927
+ }
37928
+ });
37929
+ return;
37930
+ }
37931
+ this.showCustomTimeRangeModalFallback();
37932
+ }
37933
+ showCustomTimeRangeModalFallback() {
37934
+ const prevTimePeriod = this.currentTimePeriod;
37935
+ const now = new Date();
37936
+ const defaultStart = this.fmtDatetimeLocal(this.customStartTime ? this.customStartTime.toDate()
37937
+ : new Date(now.getFullYear(), now.getMonth(), now.getDate(), 0, 0, 0));
37938
+ const defaultEnd = this.fmtDatetimeLocal(this.customEndTime ? this.customEndTime.toDate()
37939
+ : new Date(now.getFullYear(), now.getMonth(), now.getDate(), 23, 59, 59));
37940
+ const overlay = document.createElement('div');
37941
+ overlay.style.cssText = 'position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.5);z-index:9999;';
37942
+ const modal = document.createElement('div');
37943
+ const modalCss = 'position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);'
37944
+ + 'background:#fff;border-radius:4px;min-width:460px;'
37945
+ + 'box-shadow:0 4px 12px rgba(0,0,0,0.15);font-family:inherit;font-size:14px;';
37946
+ modal.style.cssText = modalCss;
37947
+ const header = document.createElement('div');
37948
+ header.style.cssText = 'display:flex;align-items:center;justify-content:space-between;padding:12px 16px;border-bottom:1px solid #e8e8e8;';
37949
+ const title = document.createElement('span');
37950
+ title.style.cssText = 'font-size:16px;font-weight:500;color:#333;';
37951
+ title.textContent = this.localization.customTimeRange || '自定义';
37952
+ const closeBtn = document.createElement('span');
37953
+ closeBtn.style.cssText = 'cursor:pointer;font-size:18px;color:#999;line-height:1;';
37954
+ closeBtn.textContent = '×';
37955
+ header.appendChild(title);
37956
+ header.appendChild(closeBtn);
37957
+ const body = document.createElement('div');
37958
+ body.style.cssText = 'padding:20px 16px;display:flex;align-items:center;flex-wrap:wrap;gap:8px;';
37959
+ const label = document.createElement('span');
37960
+ label.style.cssText = 'color:#333;white-space:nowrap;';
37961
+ label.textContent = '时间范围:';
37962
+ const inputStyle = 'border:1px solid #d9d9d9;border-radius:4px;padding:4px 8px;font-size:14px;height:32px;outline:none;color:#333;';
37963
+ const startInput = document.createElement('input');
37964
+ startInput.type = 'datetime-local';
37965
+ startInput.setAttribute('step', '1');
37966
+ startInput.value = defaultStart;
37967
+ startInput.style.cssText = inputStyle;
37968
+ const separator = document.createElement('span');
37969
+ separator.textContent = ' - ';
37970
+ separator.style.cssText = 'color:#333;';
37971
+ const endInput = document.createElement('input');
37972
+ endInput.type = 'datetime-local';
37973
+ endInput.setAttribute('step', '1');
37974
+ endInput.value = defaultEnd;
37975
+ endInput.style.cssText = inputStyle;
37976
+ body.appendChild(label);
37977
+ body.appendChild(startInput);
37978
+ body.appendChild(separator);
37979
+ body.appendChild(endInput);
37980
+ const footer = document.createElement('div');
37981
+ footer.style.cssText = 'display:flex;justify-content:flex-end;gap:8px;padding:12px 16px;border-top:1px solid #e8e8e8;';
37982
+ const cancelBtn = document.createElement('button');
37983
+ cancelBtn.textContent = this.localization.cancel || '取消';
37984
+ cancelBtn.style.cssText = 'padding:5px 16px;border:1px solid #d9d9d9;border-radius:4px;background:#fff;cursor:pointer;font-size:14px;color:#333;';
37985
+ const saveBtn = document.createElement('button');
37986
+ saveBtn.textContent = '保存';
37987
+ saveBtn.style.cssText = 'padding:5px 16px;border:none;border-radius:4px;background:#1890ff;color:#fff;cursor:pointer;font-size:14px;';
37988
+ footer.appendChild(cancelBtn);
37989
+ footer.appendChild(saveBtn);
37990
+ modal.appendChild(header);
37991
+ modal.appendChild(body);
37992
+ modal.appendChild(footer);
37993
+ overlay.appendChild(modal);
37994
+ document.body.appendChild(overlay);
37995
+ const close = (revert) => {
37996
+ document.body.removeChild(overlay);
37997
+ if (revert) {
37998
+ const selectEl = this.$element && this.$element.find('select');
37999
+ if (selectEl && selectEl.length) {
38000
+ selectEl.val(prevTimePeriod.toString());
38001
+ }
38002
+ }
38003
+ };
38004
+ closeBtn.addEventListener('click', () => close(true));
38005
+ cancelBtn.addEventListener('click', () => close(true));
38006
+ saveBtn.addEventListener('click', () => {
38007
+ const startVal = startInput.value;
38008
+ const endVal = endInput.value;
38009
+ if (!startVal) {
38010
+ startInput.style.borderColor = 'red';
38011
+ return;
38012
+ }
38013
+ const startDate = new Date(startVal);
38014
+ const endDate = endVal
38015
+ ? new Date(endVal)
38016
+ : new Date(startDate.getFullYear(), startDate.getMonth(), startDate.getDate(), 23, 59, 59);
38017
+ if (isNaN(startDate.getTime())) {
38018
+ startInput.style.borderColor = 'red';
38019
+ return;
38020
+ }
38021
+ if (endDate.getTime() <= startDate.getTime()) {
38022
+ endInput.style.borderColor = 'red';
38023
+ return;
38024
+ }
38025
+ const momentAny = moment;
38026
+ this.customStartTime = momentAny(startDate);
38027
+ this.customEndTime = momentAny(endDate);
38028
+ close(false);
38029
+ this.updateTimeRange(8);
38030
+ this.reRenderElement(this.startTime, this.endTime, this.displayOption.dataLimit, HistoricalCurveTimeRange.BeginOpenEndOpen);
38031
+ });
38032
+ }
37743
38033
  loadFirstPage() {
37744
38034
  this.updateQueryTimeRange();
37745
38035
  this.reRenderElement(this.startTime, this.endTime, this.displayOption.dataLimit, HistoricalCurveTimeRange.BeginOpenEndOpen);
@@ -74451,7 +74741,8 @@ const TIME_PERIOD_KEYS = {
74451
74741
  LAST_TWENTY_FOUR_HOURS: 'HistoricalCurve.TimeFilter.Last24Hours',
74452
74742
  LAST_SEVEN_DAYS: 'HistoricalCurve.TimeFilter.Last7Days',
74453
74743
  LAST_THIRTY_DAYS: 'HistoricalCurve.TimeFilter.Last30Days',
74454
- LAST_ONE_YEAR: 'HistoricalCurve.TimeFilter.Last1Year'
74744
+ LAST_ONE_YEAR: 'HistoricalCurve.TimeFilter.Last1Year',
74745
+ CUSTOM: 'HistoricalCurve.TimeFilter.Custom'
74455
74746
  };
74456
74747
 
74457
74748