From 15f2c6c0bacd6cdf19e79c3f668cd706a899318e Mon Sep 17 00:00:00 2001 From: balakrishna-deriv <56330681+balakrishna-deriv@users.noreply.github.com> Date: Thu, 10 Aug 2023 11:50:32 +0800 Subject: [PATCH 01/16] Bala/Flutter charts integration setup (#1325) * chore: remove chartiq * chore: add adapter store * refactor timeperiod * chore: add more calls in the adapter * fix chartType * savetoLocalstorage * chore: remove unwanted fn * update context * chore: remove stx * chore: update feed calls * chore: remove unused core * remove few props from timestore * chore: remove unused code * chore: remove chartiq related code * chore: store changes * chore: barriers integration #1 * remove barriers and integrate it with flutter * fix: last digit * feat: markers integration * chore: fix fast markers * feat: add crosshair integration * chore: set crosshair cursor * chore: fix crosshair hover * chore: add ignore css * Revert "remove barriers and integrate it with flutter" This reverts commit b043b2519d1dcf884d3db40c7b6036773cd23934. * chore: barrier integration * chore: fix digits markers placement * fix: check chart load state * fix: get interpolated tick * chore: add dataFitEnabled flag * chore: fix chart adapter granularity * chore: update isLive & dateFitEnabled * chore: add actions * fix: duplicate tick * add indicator config * add guid * feat: indicators integration * chore: transform color codes * fix: download as png and csv * refactor: use html2canvas from npm * fix: indicator configs * refactor: chart adapter store * refactor: chart interops * chore: update chart props and fix studies * refactor: remove current spot * chore: remove stx * chore: add flutter chart app * remove: chartiq scripts * chore: add chart_app to git * chore: integrate more chart types * chore: fix chart type and theme on int * chore: add indicator bars * add ma and fieldtype short code * feat: add indicator options * chore: update crosshair visibility * chore: cleanup crosshair and fix issues * chore: fix warnings * refactor * chore: remove chartiq injections * chore: trackpad zoom * chore: fix time store * chore: add documentation * refactor: currentClsoe * refactor: stores * refactor: remove ciq refs * chore: update webpack and assets * chore: format sass files * chore: remove inline source map * fix: more bugs * chore: add copy pattern * fix: contract replay request and remove comparison data * chore: follow mode check * fix: empty granularity set * fix: review comments * fix: bottom widgets height * update name id to flutter_chart_id * refactor: change name to flutter_chart_id * chore: remove console * add shortname * refactor: move web related painters * refactor: move marker icon painters * refactor: move marker painting to smartcharts * refactor: add web marker * delete studies by index * chore: add custom color for marker * chore: update marker paintings * feat: accumulators painter * chore: update color from string * restore layout after the chart is loaded * fix: restore chart * chore: add marker type * add hasPersistentBorders config * move isChartLoaded * add maxCurrentTickOffset * fix: late initialization error * fix dart issues * fix: unsubscribe issue * gator series * add macd config * chore: add indicator options * refactor: getDrawTools to func * refactor: indicator configs * fix: chartType change to undefined * fix: granularity issue * new layout id * chore: remove CIQ * chore: remove unused script * refactor: adjust timer * move epoch conversion * chore: add title * feat: add expand/collapse and move icons * fix: indicator removal and title * refactor: cleanup * refactor: add_ons_repository * fix: crosshair on restore * chore: fix drawing tools icon in the main screen * feat: add scale and scroll * chore: prevent zooming on barriers * chore: customize msPerPx and leftMargin * chore: fix clear all indicators * chore: add clear all indicators function * chore: add rainbow colors * refactor: indicators and add tooltip * fix: tooltip bugs * refoctor: fix typo and add gator tooltip * chore: add macd tooltips and refactor * chore: add smi tooltip values * chore: add indicators * fix: index out of bound exception * chore: switch theme for indicators * fix: tslint issue * fix: channel fix and donchian * chore: add pipsize * fix: indicator bugs * chore: change to upper case * fix: feed issue for 2 second intervals * fix: crosshair decimal places * fix: new tick barrier * chore: fix dark theme container * fix: high/low barrier width * fix: feed issue * chore: fix draggable barrier * chore: fix barrier issues and symbol close * refactor: helpers * fix: price line width * fix: price line width * fix: chart live status update on contract close * chore: fix chart close issue * chore: remove lodash dependency * chore: add showLastIndicator config * chore: add decimal places for indicator tooltip content * fix: adx indicator config * refactor: initial chart data loaded * chore: set initial chart data * chore: add mount prop * chore: add yAxisWidth to api * refactor: chart_app * refactor: chart app * chore: pass isMobile to drawings * fix: indicator index and accumulator drawing * chore: fix streams * fix: feed issue * refactor: controller functions * refactor: scale * fix: prevent datafit contract from being scrolled * chore: add chart margin * fix: tick contract issue * fix: vertical padding fraction * fix: granularity issues * fix: offset issue * chore: reset msPerPx on granularity change * fix: chart type change reaction * fix: chart type issue * fix: contract close reload issue * chore: paint on each line series paint * fix: late init * fix: template restore * chore: ignore chart_app bundle * fix: min interval width * chore: fix tick animation in data fit mode * chore: fix data fit mode in 2s ticks * fix: accumulator height offset * fix: alternative source * refactor: controller getter * fix: field options * chore: check granularity for chart style * refactor: accumulator contract painting * chore: add fast marker limit * chore: add fast marker limit #2 * chore: add wrapper controller * chore: add PainterProps * chore: add opacity and zoom * fix: font size * fix: types * fix: lint errors * fix: eslint errors * chore: refactor chart controller * fix: catch all errors * chore: add binary search * fix: crosshair position * chore: add max interval width for candles style * chore: add webpack ignore * chore: simplify network reconnect * chore: add loading animation color * chore: fix mobile scaling * chore: remove touch listeners * chore: add scroll to recent component * chore: add crosshair getter functions * chore: indicator index * chore: clamp deltaY values to fix scaling on wheel * chore: add rainbow line styles --------- Co-authored-by: balakrishna-binary <56330681+balakrishna-binary@users.noreply.github.com> --- .eslintignore | 1 - .gitignore | 2 + .npmignore | 2 +- .stylelintrc | 225 +- app/index.html | 34 +- app/index.tsx | 14 +- app/test.tsx | 9 +- chart_app/.gitignore | 107 + chart_app/.metadata | 10 + chart_app/README.md | 16 + chart_app/analysis_options.yaml | 4 + chart_app/lib/main.dart | 277 + .../lib/src/add_ons/add_ons_repository.dart | 95 + chart_app/lib/src/chart_app.dart | 101 + chart_app/lib/src/helpers/chart.dart | 22 + chart_app/lib/src/helpers/color.dart | 49 + chart_app/lib/src/helpers/marker_painter.dart | 19 + chart_app/lib/src/helpers/series.dart | 45 + chart_app/lib/src/interop/dart_interop.dart | 202 + chart_app/lib/src/interop/js_interop.dart | 183 + .../paint_functions/paint_end_marker.dart | 96 + .../paint_functions/paint_start_line.dart | 37 + .../paint_functions/paint_start_marker.dart | 22 + .../paint_functions/paint_vertical_line.dart | 27 + chart_app/lib/src/markers/marker_group.dart | 46 + .../markers/marker_group_icon_painter.dart | 29 + .../lib/src/markers/marker_group_painter.dart | 58 + .../lib/src/markers/marker_group_series.dart | 62 + .../accumulator_marker_icon_painter.dart | 245 + .../digit_marker_icon_painter.dart | 163 + .../tick_marker_icon_painter.dart | 220 + chart_app/lib/src/markers/painter_props.dart | 14 + chart_app/lib/src/markers/web_marker.dart | 61 + .../lib/src/misc/crosshair_controller.dart | 64 + .../lib/src/misc/wrapped_controller.dart | 119 + chart_app/lib/src/models/chart_config.dart | 158 + chart_app/lib/src/models/chart_feed.dart | 104 + chart_app/lib/src/models/indicators.dart | 356 + .../lib/src/painters/custom_line_painter.dart | 21 + .../src/series/current_tick_indicator.dart | 21 + .../lib/src/series/custom_line_series.dart | 23 + chart_app/pubspec.yaml | 25 + chart_app/web/favicon.png | Bin 0 -> 917 bytes chart_app/web/index.html | 100 + chart_app/web/manifest.json | 12 + chartiq/development/index.js | 5 - chartiq/development/js/addOns.js | 4982 --- chartiq/development/js/advanced.js | 16390 ---------- chartiq/development/js/chartiq.js | 22205 -------------- chartiq/development/js/deprecated.js | 2864 -- chartiq/development/js/standard.js | 25348 ---------------- chartiq/hammer.js | 6 - chartiq/html2canvas.min.js | 20 - chartiq/intl.js | 2952 -- chartiq/production/index.js | 5 - chartiq/production/js/addOns.js | 4982 --- chartiq/production/js/advanced.js | 25 - chartiq/production/js/chartiq.js | 88 - chartiq/production/js/deprecated.js | 2864 -- chartiq/production/js/standard.js | 25 - chartiq/splines.js | 129 - declarations.d.ts | 1 - package-lock.json | 20885 ++++++++++++- package.json | 4 + sass/components/_barrier.scss | 28 +- sass/components/_crosshair.scss | 86 +- sass/components/_toolbar-widget.scss | 1 - sass/styles/chart.scss | 345 +- sass/styles/main.scss | 27 +- scripts/extract-indicator-translations.js | 21 - src/Constant.tsx | 1749 +- src/SplinePlotter.ts | 95 - src/binaryapi/ActiveSymbols.ts | 45 +- src/binaryapi/BinaryAPI.ts | 2 +- src/chartiq_injections/backingStore.ts | 37 - src/chartiq_injections/calculateAwesome.ts | 31 - src/chartiq_injections/createXAxis.ts | 13 - src/chartiq_injections/currentHR.ts | 98 - .../drawingClickChartEngine.ts | 54 - src/chartiq_injections/findHighlights.ts | 545 - src/chartiq_injections/headsUpHR.ts | 11 - src/chartiq_injections/index.ts | 38 - .../manageMasterDataLength.ts | 15 - src/chartiq_injections/plotterDrawText.ts | 37 - src/chartiq_injections/renderChannel.ts | 49 - src/chartiq_injections/renderEllipse.ts | 55 - src/chartiq_injections/renderGartley.ts | 86 - src/chartiq_injections/renderPitchfork.ts | 64 - src/chartiq_injections/renderRectangle.ts | 53 - src/chartiq_injections/renderSegment.ts | 92 - src/chartiq_injections/resizeObserver.ts | 38 - src/chartiq_injections/setMeasure.ts | 95 - src/components/Barrier.tsx | 15 +- src/components/BottomWidgetsContainer.tsx | 8 +- src/components/Chart.tsx | 30 +- src/components/ChartMode.tsx | 9 +- src/components/ChartTypes.tsx | 4 +- src/components/Crosshair.tsx | 53 +- src/components/DrawTools.tsx | 8 +- src/components/FastMarker.tsx | 103 +- src/components/Form.tsx | 34 +- src/components/NavigationWidget.tsx | 15 +- src/components/PriceLine.tsx | 8 +- src/components/RawMarker.tsx | 166 - src/components/RenderInsideChart.tsx | 7 +- src/components/ScrollToRecent.tsx | 35 + src/components/SettingsDialog.tsx | 141 +- src/components/StudyLegend.tsx | 119 +- src/components/Timeperiod.tsx | 40 +- .../categoricaldisplay/FilterPanel.tsx | 49 +- .../categoricaldisplay/ResultsPanel.tsx | 4 +- src/components/ui/Animation.ts | 339 - src/components/ui/Context.ts | 86 +- src/components/ui/Helper.ts | 41 - src/components/ui/Keystroke.ts | 226 - src/components/ui/KeystrokeHub.ts | 173 - src/components/ui/utils.ts | 16 + src/feed/Feed.ts | 406 +- src/feed/TickHistoryFormatter.ts | 28 +- src/feed/subscription/DelayedSubscription.ts | 5 +- src/feed/subscription/RealtimeSubscription.ts | 7 +- src/feed/subscription/Subscription.ts | 5 +- src/flutter-chart/index.ts | 34 + src/flutter-chart/painter.ts | 18 + src/index.ts | 3 +- src/overrides.ts | 10 + src/store/BarrierStore.ts | 116 +- src/store/BottomWidgetsContainerStore.ts | 59 +- src/store/ChartAdapterStore.ts | 332 + src/store/ChartSettingStore.ts | 16 +- src/store/ChartSizeStore.ts | 27 +- src/store/ChartState.ts | 369 +- src/store/ChartStore.ts | 912 +- src/store/ChartTypeStore.ts | 197 +- src/store/CrosshairStore.ts | 543 +- src/store/CurrentSpotStore.ts | 91 - src/store/DrawToolsStore.ts | 144 +- src/store/FavoriteStore.ts | 6 +- src/store/HighestLowestStore.ts | 41 +- src/store/IndicatorPredictionDialogStore.ts | 3 +- src/store/LastDigitStatsStore.ts | 34 +- src/store/MarkerStore.ts | 264 +- src/store/MenuStore.ts | 15 +- src/store/NavigationWidgetStore.ts | 31 +- src/store/PaginationLoaderStore.ts | 20 +- src/store/PriceLineStore.ts | 145 +- src/store/SettingsDialogStore.ts | 58 +- src/store/ShareStore.ts | 24 +- src/store/StudyLegendStore.ts | 536 +- src/store/TimeperiodStore.ts | 134 +- src/store/ToolbarWidgetStore.ts | 8 +- src/store/ViewStore.ts | 115 +- src/store/index.ts | 5 +- src/types/chartiq.types.ts | 20 - src/types/index.ts | 1 - src/types/props.types.ts | 291 +- src/types/stores.types.ts | 4 +- src/utils/ServerTime.ts | 3 +- src/utils/date.ts | 100 + src/utils/index.ts | 277 +- sw.js | 53 +- webpack.config.js | 33 +- 162 files changed, 28655 insertions(+), 89707 deletions(-) create mode 100644 chart_app/.gitignore create mode 100644 chart_app/.metadata create mode 100644 chart_app/README.md create mode 100644 chart_app/analysis_options.yaml create mode 100644 chart_app/lib/main.dart create mode 100644 chart_app/lib/src/add_ons/add_ons_repository.dart create mode 100644 chart_app/lib/src/chart_app.dart create mode 100644 chart_app/lib/src/helpers/chart.dart create mode 100644 chart_app/lib/src/helpers/color.dart create mode 100644 chart_app/lib/src/helpers/marker_painter.dart create mode 100644 chart_app/lib/src/helpers/series.dart create mode 100644 chart_app/lib/src/interop/dart_interop.dart create mode 100644 chart_app/lib/src/interop/js_interop.dart create mode 100644 chart_app/lib/src/markers/helpers/paint_functions/paint_end_marker.dart create mode 100644 chart_app/lib/src/markers/helpers/paint_functions/paint_start_line.dart create mode 100644 chart_app/lib/src/markers/helpers/paint_functions/paint_start_marker.dart create mode 100644 chart_app/lib/src/markers/helpers/paint_functions/paint_vertical_line.dart create mode 100644 chart_app/lib/src/markers/marker_group.dart create mode 100644 chart_app/lib/src/markers/marker_group_icon_painter.dart create mode 100644 chart_app/lib/src/markers/marker_group_painter.dart create mode 100644 chart_app/lib/src/markers/marker_group_series.dart create mode 100644 chart_app/lib/src/markers/marker_icon_painters/accumulator_marker_icon_painter.dart create mode 100644 chart_app/lib/src/markers/marker_icon_painters/digit_marker_icon_painter.dart create mode 100644 chart_app/lib/src/markers/marker_icon_painters/tick_marker_icon_painter.dart create mode 100644 chart_app/lib/src/markers/painter_props.dart create mode 100644 chart_app/lib/src/markers/web_marker.dart create mode 100644 chart_app/lib/src/misc/crosshair_controller.dart create mode 100644 chart_app/lib/src/misc/wrapped_controller.dart create mode 100644 chart_app/lib/src/models/chart_config.dart create mode 100644 chart_app/lib/src/models/chart_feed.dart create mode 100644 chart_app/lib/src/models/indicators.dart create mode 100644 chart_app/lib/src/painters/custom_line_painter.dart create mode 100644 chart_app/lib/src/series/current_tick_indicator.dart create mode 100644 chart_app/lib/src/series/custom_line_series.dart create mode 100644 chart_app/pubspec.yaml create mode 100644 chart_app/web/favicon.png create mode 100644 chart_app/web/index.html create mode 100644 chart_app/web/manifest.json delete mode 100644 chartiq/development/index.js delete mode 100644 chartiq/development/js/addOns.js delete mode 100644 chartiq/development/js/advanced.js delete mode 100644 chartiq/development/js/chartiq.js delete mode 100644 chartiq/development/js/deprecated.js delete mode 100644 chartiq/development/js/standard.js delete mode 100644 chartiq/hammer.js delete mode 100644 chartiq/html2canvas.min.js delete mode 100644 chartiq/intl.js delete mode 100644 chartiq/production/index.js delete mode 100644 chartiq/production/js/addOns.js delete mode 100644 chartiq/production/js/advanced.js delete mode 100644 chartiq/production/js/chartiq.js delete mode 100644 chartiq/production/js/deprecated.js delete mode 100644 chartiq/production/js/standard.js delete mode 100644 chartiq/splines.js delete mode 100644 scripts/extract-indicator-translations.js delete mode 100644 src/SplinePlotter.ts delete mode 100644 src/chartiq_injections/backingStore.ts delete mode 100644 src/chartiq_injections/calculateAwesome.ts delete mode 100644 src/chartiq_injections/createXAxis.ts delete mode 100644 src/chartiq_injections/currentHR.ts delete mode 100644 src/chartiq_injections/drawingClickChartEngine.ts delete mode 100644 src/chartiq_injections/findHighlights.ts delete mode 100644 src/chartiq_injections/headsUpHR.ts delete mode 100644 src/chartiq_injections/index.ts delete mode 100644 src/chartiq_injections/manageMasterDataLength.ts delete mode 100644 src/chartiq_injections/plotterDrawText.ts delete mode 100644 src/chartiq_injections/renderChannel.ts delete mode 100644 src/chartiq_injections/renderEllipse.ts delete mode 100644 src/chartiq_injections/renderGartley.ts delete mode 100644 src/chartiq_injections/renderPitchfork.ts delete mode 100644 src/chartiq_injections/renderRectangle.ts delete mode 100644 src/chartiq_injections/renderSegment.ts delete mode 100644 src/chartiq_injections/resizeObserver.ts delete mode 100644 src/chartiq_injections/setMeasure.ts delete mode 100644 src/components/RawMarker.tsx create mode 100644 src/components/ScrollToRecent.tsx delete mode 100644 src/components/ui/Animation.ts delete mode 100644 src/components/ui/Helper.ts delete mode 100644 src/components/ui/Keystroke.ts delete mode 100644 src/components/ui/KeystrokeHub.ts create mode 100644 src/flutter-chart/index.ts create mode 100644 src/flutter-chart/painter.ts create mode 100644 src/overrides.ts create mode 100644 src/store/ChartAdapterStore.ts delete mode 100644 src/store/CurrentSpotStore.ts delete mode 100644 src/types/chartiq.types.ts create mode 100644 src/utils/date.ts diff --git a/.eslintignore b/.eslintignore index 2ab769f474..53488d86cf 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,5 +1,4 @@ dist -chartiq node_modules design /**/*.d.ts diff --git a/.gitignore b/.gitignore index dcdf390764..f5a5f4b243 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,5 @@ jsconfig.json CNAME .nyc_output **/*.swp + +chart_app/build/** diff --git a/.npmignore b/.npmignore index 6d551c36a8..7a4bfc70ba 100644 --- a/.npmignore +++ b/.npmignore @@ -3,7 +3,7 @@ .eslintrc .tern-project .travis.yml -chartiq/ +chart_app/ crowdin.yml css/ design-v1.js diff --git a/.stylelintrc b/.stylelintrc index 88fe9d5d13..e57a1d0677 100644 --- a/.stylelintrc +++ b/.stylelintrc @@ -1,88 +1,141 @@ { - "plugins": [ - "stylelint-no-unsupported-browser-features" - ], - "rules": { - "at-rule-name-case" : "lower", - "at-rule-name-space-after" : "always", - "at-rule-semicolon-newline-after" : "always", - "block-closing-brace-newline-after" : "always", - "block-closing-brace-newline-before" : "always", - "block-no-empty" : true, - "block-opening-brace-newline-after" : "always", - "block-opening-brace-space-before" : "always", - "color-hex-case" : "lower", - "color-named" : "never", - "color-no-invalid-hex" : true, - "declaration-bang-space-after" : "never", - "declaration-bang-space-before" : "always", - "declaration-block-no-duplicate-properties" : [true, { "ignore": ["consecutive-duplicates"] }], - "declaration-block-no-shorthand-property-overrides": true, - "declaration-block-semicolon-newline-after" : "always", - "declaration-block-semicolon-newline-before" : "never-multi-line", - "declaration-block-semicolon-space-after" : "always-single-line", - "declaration-block-semicolon-space-before" : "never", - "declaration-block-trailing-semicolon" : "always", - "declaration-colon-space-after" : "always", - "declaration-colon-space-before" : "never", - "font-family-name-quotes" : "always-unless-keyword", - "function-calc-no-unspaced-operator" : true, - "function-comma-space-after" : "always", - "function-comma-space-before" : "never", - "function-name-case" : "lower", - "function-parentheses-space-inside" : "never", - "function-url-quotes" : "always", - "indentation" : 4, - "max-empty-lines" : 1, - "media-feature-colon-space-after" : "always", - "media-feature-colon-space-before" : "never", - "media-feature-range-operator-space-after" : "always", - "media-feature-range-operator-space-before" : "always", - "media-query-list-comma-newline-after" : "never-multi-line", - "media-query-list-comma-newline-before" : "never-multi-line", - "media-query-list-comma-space-after" : "always", - "media-query-list-comma-space-before" : "never", - "no-duplicate-selectors" : true, - "no-eol-whitespace" : true, - "no-extra-semicolons" : true, - "no-invalid-double-slash-comments" : true, - "number-leading-zero" : "always", - "number-max-precision" : 2, - "number-no-trailing-zeros" : true, - "property-case" : "lower", - "plugin/no-unsupported-browser-features" : [true, { - "severity": "error", - "ignore": ["calc", "user-select-none", "multicolumn", "css-appearance", "intrinsic-width"] - }], - "rule-empty-line-before" : ["always", { "ignore": ["after-comment"], "except": ["inside-block-and-after-rule", "first-nested"] }], - "selector-attribute-brackets-space-inside" : "never", - "selector-attribute-operator-space-after" : "never", - "selector-attribute-operator-space-before" : "never", - "selector-combinator-space-after" : "always", - "selector-combinator-space-before" : "always", - "selector-list-comma-newline-before" : "never-multi-line", - "selector-list-comma-space-before" : "never", - "selector-max-empty-lines" : 0, - "selector-pseudo-class-case" : "lower", - "selector-pseudo-class-no-unknown" : [true, { - "ignorePseudoClasses": ["export"] - }], - "selector-pseudo-class-parentheses-space-inside" : "never", - "selector-pseudo-element-case" : "lower", - "selector-pseudo-element-colon-notation" : "single", - "selector-pseudo-element-no-unknown" : true, - "selector-type-case" : "lower", - "selector-type-no-unknown" : [true, { "ignoreTypes": ["from", "to", "0%", "50%", "100%", "_"] }], - "shorthand-property-no-redundant-values" : true, - "string-no-newline" : true, - "string-quotes" : "single", - "time-min-milliseconds" : 100, - "unit-case" : "lower", - "unit-whitelist" : ["fr", "px", "em", "rem", "%", "vw", "vh", "deg", "ms", "s"], - "value-keyword-case" : "lower", - "value-list-comma-newline-after" : "never-multi-line", - "value-list-comma-newline-before" : "never-multi-line", - "value-list-comma-space-after" : "always", - "value-list-comma-space-before" : "never" - } + "plugins": [ + "stylelint-no-unsupported-browser-features" + ], + "rules": { + "at-rule-name-case": "lower", + "at-rule-name-space-after": "always", + "at-rule-semicolon-newline-after": "always", + "block-closing-brace-newline-after": "always", + "block-closing-brace-newline-before": "always", + "block-no-empty": true, + "block-opening-brace-newline-after": "always", + "block-opening-brace-space-before": "always", + "color-hex-case": "lower", + "color-named": "never", + "color-no-invalid-hex": true, + "declaration-bang-space-after": "never", + "declaration-bang-space-before": "always", + "declaration-block-no-duplicate-properties": [ + true, + { + "ignore": [ + "consecutive-duplicates" + ] + } + ], + "declaration-block-no-shorthand-property-overrides": true, + "declaration-block-semicolon-newline-after": "always", + "declaration-block-semicolon-newline-before": "never-multi-line", + "declaration-block-semicolon-space-after": "always-single-line", + "declaration-block-semicolon-space-before": "never", + "declaration-block-trailing-semicolon": "always", + "declaration-colon-space-after": "always", + "declaration-colon-space-before": "never", + "font-family-name-quotes": "always-unless-keyword", + "function-calc-no-unspaced-operator": true, + "function-comma-space-before": "never", + "function-name-case": "lower", + "function-url-quotes": "always", + "indentation": 4, + "max-empty-lines": 1, + "media-feature-colon-space-after": "always", + "media-feature-colon-space-before": "never", + "media-feature-range-operator-space-after": "always", + "media-feature-range-operator-space-before": "always", + "media-query-list-comma-newline-after": "never-multi-line", + "media-query-list-comma-newline-before": "never-multi-line", + "media-query-list-comma-space-after": "always", + "media-query-list-comma-space-before": "never", + "no-duplicate-selectors": true, + "no-eol-whitespace": true, + "no-extra-semicolons": true, + "no-invalid-double-slash-comments": true, + "number-leading-zero": "always", + "number-max-precision": 2, + "number-no-trailing-zeros": true, + "property-case": "lower", + "plugin/no-unsupported-browser-features": [ + true, + { + "severity": "error", + "ignore": [ + "calc", + "user-select-none", + "multicolumn", + "css-appearance", + "intrinsic-width" + ] + } + ], + "rule-empty-line-before": [ + "always", + { + "ignore": [ + "after-comment" + ], + "except": [ + "inside-block-and-after-rule", + "first-nested" + ] + } + ], + "selector-attribute-brackets-space-inside": "never", + "selector-attribute-operator-space-after": "never", + "selector-attribute-operator-space-before": "never", + "selector-combinator-space-after": "always", + "selector-combinator-space-before": "always", + "selector-list-comma-newline-before": "never-multi-line", + "selector-list-comma-space-before": "never", + "selector-max-empty-lines": 0, + "selector-pseudo-class-case": "lower", + "selector-pseudo-class-no-unknown": [ + true, + { + "ignorePseudoClasses": [ + "export" + ] + } + ], + "selector-pseudo-class-parentheses-space-inside": "never", + "selector-pseudo-element-case": "lower", + "selector-pseudo-element-colon-notation": "single", + "selector-pseudo-element-no-unknown": true, + "selector-type-case": "lower", + "selector-type-no-unknown": [ + true, + { + "ignoreTypes": [ + "from", + "to", + "0%", + "50%", + "100%", + "_" + ] + } + ], + "shorthand-property-no-redundant-values": true, + "string-no-newline": true, + "string-quotes": "single", + "time-min-milliseconds": 100, + "unit-case": "lower", + "unit-whitelist": [ + "fr", + "px", + "em", + "rem", + "%", + "vw", + "vh", + "deg", + "ms", + "s" + ], + "value-keyword-case": "lower", + "value-list-comma-newline-after": "never-multi-line", + "value-list-comma-newline-before": "never-multi-line", + "value-list-comma-space-after": "always", + "value-list-comma-space-before": "never" + } } diff --git a/app/index.html b/app/index.html index ac95dfb7e5..f003d6dcb6 100644 --- a/app/index.html +++ b/app/index.html @@ -1,20 +1,20 @@ - + +
+ + + + + + +loadChart
callback.
- * Here are a few examples of rendering parameters:
- * ``` - * // Assuming a PlotComplementer declared as "forecaster": - * forecaster.renderingParameters = {chartType:"scatterplot", opacity:0.5, field:"Certainty"} - * forecaster.renderingParameters = {chartType:"histogram", border_color:"transparent", opacity:0.3} - * forecaster.renderingParameters = {chartType:"channel", opacity:0.5, pattern:"dotted"} - * forecaster.renderingParameters = {chartType:"candle", opacity:0.5, color:"blue", border_color:"blue"} - * ``` - * - * @constructor - * @name CIQ.PlotComplementer - * @since 7.3.0 - * - * @exampleThis `filter` function is like the `filter` in basic quote feeds. - * See {@link CIQ.ChartEngine#attachQuoteFeed} for more information on quote feed - * `filter` functions.
- * @alias setQuoteFeed - * @memberof CIQ.PlotComplementer.prototype - * @since 7.3.0 - */ - this.setQuoteFeed = function (params) { - if (!params.quoteFeed || !params.behavior) return; - var behavior = CIQ.clone(params.behavior); - behavior.generator = this.id; - var existingFilter = params.filter; - var filter = function (params) { - if (existingFilter && !existingFilter(params)) return false; - return params.symbolObject.generator == behavior.generator; - }; - stx.attachQuoteFeed(params.quoteFeed, behavior, filter); - this.quoteFeed = params.quoteFeed; - }; - - this.setQuoteFeed(params); - this.resetRenderingParameters(); - }; - -}; - - -let _exports = {CIQ:__CIQ_}; -export {__js_addons_standard_extendedHours_ as extendedHours}; -export {__js_addons_standard_fullScreen_ as fullScreen}; -export {__js_addons_standard_inactivityTimer_ as inactivityTimer}; -export {__js_addons_standard_rangeSlider_ as rangeSlider}; -export {__js_addons_standard_shortcuts_ as shortcuts}; -export {__js_addons_standard_tableView_ as tableView}; -export {__js_addons_standard_tooltip_ as tooltip}; -export {__js_addons_advanced_animation_ as animation}; -export {__js_addons_advanced_continuousZoom_ as continuousZoom}; -export {__js_addons_advanced_outliers_ as outliers}; -export {__js_addons_advanced_plotComplementer_ as plotComplementer}; - -export {__CIQ_ as CIQ}; - -/* global __TREE_SHAKE__ */ -if (typeof __TREE_SHAKE__ === "undefined" || !__TREE_SHAKE__) { - _exports.CIQ.activateImports( - __js_addons_standard_extendedHours_, - __js_addons_standard_fullScreen_, - __js_addons_standard_inactivityTimer_, - __js_addons_standard_rangeSlider_, - __js_addons_standard_shortcuts_, - __js_addons_standard_tableView_, - __js_addons_standard_tooltip_, - __js_addons_advanced_animation_, - __js_addons_advanced_continuousZoom_, - __js_addons_advanced_outliers_, - __js_addons_advanced_plotComplementer_, - null - ); -} \ No newline at end of file diff --git a/chartiq/development/js/advanced.js b/chartiq/development/js/advanced.js deleted file mode 100644 index ac8329ed1b..0000000000 --- a/chartiq/development/js/advanced.js +++ /dev/null @@ -1,16390 +0,0 @@ -/***************************************************************************! - WARNING: this file is for internal development and debugging purposes only! - It may *not* be posted publicly under any circumstances without explicit - consent from ChartIQ. -****************************************************************************/ -/**! - * 8.2.0 - * Generation date: 2023-03-23T15:05:01.971Z - * Client name: deriv limited - * Package Type: Technical Analysis - * License type: annual - * Expiration date: "2024/04/01" - * Domain lock: ["127.0.0.1","localhost","deriv.com","deriv.app","deriv.me","binary.com","binary.sx","binary.me","binary.bot","deriv.be"] - * iFrame lock: true - */ - -/***********************************************************! - * Copyright by ChartIQ, Inc. - * Licensed under the ChartIQ, Inc. Developer License Agreement https://www.chartiq.com/developer-license-agreement -*************************************************************/ -/*************************************! DO NOT MAKE CHANGES TO THIS LIBRARY FILE!! !************************************* -* If you wish to overwrite default functionality, create a separate file with a copy of the methods you are overwriting * -* and load that file right after the library has been loaded, but before the chart engine is instantiated. * -* Directly modifying library files will prevent upgrades and the ability for ChartIQ to support your solution. * -*************************************************************************************************************************/ -/* eslint-disable no-extra-parens */ - - -import {CIQ as __CIQ_, SplinePlotter as __SplinePlotter_, timezoneJS as __timezoneJS_, $$ as __$$_, $$$ as __$$$_} from "../js/standard.js"; - - -let __js_advanced_drawingAdvanced_ = (_exports) => { - -/* global _CIQ, _timezoneJS, _SplinePlotter */ - - -var CIQ = typeof _CIQ !== "undefined" ? _CIQ : _exports.CIQ; -var timezoneJS = - typeof _timezoneJS !== "undefined" ? _timezoneJS : _exports.timezoneJS; - -if (!CIQ.Drawing) { - console.error( - "drawingAdvanced feature requires first activating drawing feature." - ); -} else { - /** - * Ray drawing tool. A ray is defined by two points. It travels infinitely past the second point. - * - * It inherits its properties from {@link CIQ.Drawing.line}. - * @constructor - * @name CIQ.Drawing.ray - */ - CIQ.Drawing.ray = function () { - this.name = "ray"; - }; - - CIQ.inheritsFrom(CIQ.Drawing.ray, CIQ.Drawing.line); - - CIQ.Drawing.ray.prototype.calculateOuterSet = function (panel) { - if ( - this.p0[0] == this.p1[0] || - this.p0[1] == this.p1[1] || - CIQ.ChartEngine.isDailyInterval(this.stx.layout.interval) - ) { - return; - } - - var vector = { - x0: this.p0[0], - y0: this.p0[1], - x1: this.p1[0], - y1: this.p1[1] - }; - - var endOfRay = vector.x1 + 1000; - if (vector.x0 > vector.x1) { - endOfRay = vector.x1 - 1000; - } - - this.v0B = this.v0; - this.v1B = CIQ.yIntersection(vector, endOfRay); - this.d0B = this.d0; - this.d1B = this.stx.dateFromTick(endOfRay, panel.chart); - }; - - CIQ.Drawing.ray.prototype.adjust = function () { - var panel = this.stx.panels[this.panelName]; - if (!panel) return; - this.setPoint(0, this.d0, this.v0, panel.chart); - this.setPoint(1, this.d1, this.v1, panel.chart); - // Use outer set if original drawing was on intraday but now displaying on daily - if (CIQ.ChartEngine.isDailyInterval(this.stx.layout.interval) && this.d0B) { - this.setPoint(1, this.d1B, this.v1B, panel.chart); - } - }; - - /** - * Continuous line drawing tool. Creates a series of connected line segments, each one completed with a user click. - * - * It inherits its properties from {@link CIQ.Drawing.segment}. - * @constructor - * @name CIQ.Drawing.continuous - */ - CIQ.Drawing.continuous = function () { - this.name = "continuous"; - this.dragToDraw = false; - this.maxSegments = null; - }; - - CIQ.inheritsFrom(CIQ.Drawing.continuous, CIQ.Drawing.segment); - - CIQ.Drawing.continuous.prototype.click = function (context, tick, value) { - var panel = this.stx.panels[this.panelName]; - if (!panel) return; - this.copyConfig(); - if (!this.penDown) { - this.setPoint(0, tick, value, panel.chart); - this.penDown = true; - return false; - } - if (this.accidentalClick(tick, value)) { - this.stx.undo(); //abort - return true; - } - - this.setPoint(1, tick, value, panel.chart); - - // render a segment - var Segment = CIQ.Drawing.segment; - var segment = new Segment(); - var obj = this.serialize(this.stx); - segment.reconstruct(this.stx, obj); - this.stx.addDrawing(segment); - this.stx.changeOccurred("vector"); - this.stx.draw(); - this.segment++; - - if (this.maxSegments && this.segment > this.maxSegments) return true; - this.setPoint(0, tick, value, panel.chart); // reset initial point for next segment, copy by value - return false; - }; - - /** - * Ellipse drawing tool. - * - * It inherits its properties from {@link CIQ.Drawing.BaseTwoPoint}. - * @constructor - * @name CIQ.Drawing.ellipse - */ - CIQ.Drawing.ellipse = function () { - this.name = "ellipse"; - }; - - CIQ.inheritsFrom(CIQ.Drawing.ellipse, CIQ.Drawing.BaseTwoPoint); - - CIQ.Drawing.ellipse.prototype.render = function (context) { - var panel = this.stx.panels[this.panelName]; - if (!panel) return; - var x0 = this.stx.pixelFromTick(this.p0[0], panel.chart); - var x1 = this.stx.pixelFromTick(this.p1[0], panel.chart); - var y0 = this.stx.pixelFromValueAdjusted(panel, this.p0[0], this.p0[1]); - var y1 = this.stx.pixelFromValueAdjusted(panel, this.p1[0], this.p1[1]); - - var left = x0 - (x1 - x0); - var right = x1; - var middle = y0; - var bottom = y1; - var top = y0 - (y1 - y0); - var weight = (bottom - top) / 6; - var lineWidth = this.lineWidth; - if (!lineWidth) lineWidth = 1.1; - var edgeColor = this.color; - if (edgeColor == "auto" || CIQ.isTransparent(edgeColor)) - edgeColor = this.stx.defaultColor; - if (this.highlighted) { - edgeColor = this.stx.getCanvasColor("stx_highlight_vector"); - if (lineWidth == 0.1) lineWidth = 1.1; - } - - var fillColor = this.fillColor; - - context.beginPath(); - context.moveTo(left, middle); - context.bezierCurveTo( - left, - bottom + weight, - right, - bottom + weight, - right, - middle - ); - context.bezierCurveTo( - right, - top - weight, - left, - top - weight, - left, - middle - ); - - if (fillColor && !CIQ.isTransparent(fillColor) && fillColor != "auto") { - context.fillStyle = fillColor; - context.globalAlpha = 0.2; - context.fill(); - context.globalAlpha = 1; - } - - if (edgeColor && this.pattern != "none") { - context.strokeStyle = edgeColor; - context.lineWidth = lineWidth; - if (context.setLineDash) { - context.setLineDash(CIQ.borderPatternToArray(lineWidth, this.pattern)); - context.lineDashOffset = 0; //start point in array - } - context.stroke(); - } - context.closePath(); - if (this.highlighted) { - var p1Fill = this.highlighted == "p1" ? true : false; - this.littleCircle(context, x1, y1, p1Fill); - } - }; - - CIQ.Drawing.ellipse.prototype.intersected = function (tick, value, box) { - if (!this.p0 || !this.p1) return null; // in case invalid drawing (such as from panel that no longer exists) - if (this.pointIntersection(this.p1[0], this.p1[1], box)) { - this.highlighted = "p1"; - return { - action: "drag", - point: "p1" - }; - } - var left = this.p0[0] - (this.p1[0] - this.p0[0]); - var right = this.p1[0]; - var bottom = this.p1[1]; - var top = this.p0[1] - (this.p1[1] - this.p0[1]); - - if (box.x0 > Math.max(left, right) || box.x1 < Math.min(left, right)) - return false; - if (box.y1 > Math.max(top, bottom) || box.y0 < Math.min(top, bottom)) - return false; - this.highlighted = true; - return { - action: "move", - p0: CIQ.clone(this.p0), - p1: CIQ.clone(this.p1), - tick: tick, - value: value - }; - }; - - CIQ.Drawing.ellipse.prototype.configs = [ - "color", - "fillColor", - "lineWidth", - "pattern" - ]; - - /** - * Reconstruct an ellipse - * @param {CIQ.ChartEngine} stx The chart object - * @param {object} [obj] A drawing descriptor - * @param {string} [obj.col] The border color - * @param {string} [obj.fc] The fill color - * @param {string} [obj.pnl] The panel name - * @param {string} [obj.ptrn] Optional pattern for line "solid","dotted","dashed". Defaults to solid. - * @param {number} [obj.lw] Optional line width. Defaults to 1. - * @param {number} [obj.v0] Value (price) for the center point - * @param {number} [obj.v1] Value (price) for the outside point - * @param {number} [obj.d0] Date (string form) for the center point - * @param {number} [obj.d1] Date (string form) for the outside point - * @param {number} [obj.tzo0] Offset of UTC from d0 in minutes - * @param {number} [obj.tzo1] Offset of UTC from d1 in minutes - * @memberOf CIQ.Drawing.ellipse - */ - CIQ.Drawing.ellipse.prototype.reconstruct = function (stx, obj) { - this.stx = stx; - this.color = obj.col; - this.fillColor = obj.fc; - this.panelName = obj.pnl; - this.pattern = obj.ptrn; - this.lineWidth = obj.lw; - this.d0 = obj.d0; - this.d1 = obj.d1; - this.tzo0 = obj.tzo0; - this.tzo1 = obj.tzo1; - this.v0 = obj.v0; - this.v1 = obj.v1; - this.adjust(); - }; - - CIQ.Drawing.ellipse.prototype.serialize = function () { - return { - name: this.name, - pnl: this.panelName, - col: this.color, - fc: this.fillColor, - ptrn: this.pattern, - lw: this.lineWidth, - d0: this.d0, - d1: this.d1, - tzo0: this.tzo0, - tzo1: this.tzo1, - v0: this.v0, - v1: this.v1 - }; - }; - - /** - * Channel drawing tool. Creates a channel within 2 parallel line segments. - * - * It inherits its properties from {@link CIQ.Drawing.segment}. - * @constructor - * @name CIQ.Drawing.channel - * @version ChartIQ Advanced Package - */ - CIQ.Drawing.channel = function () { - this.name = "channel"; - this.dragToDraw = false; - this.p2 = null; - }; - - CIQ.inheritsFrom(CIQ.Drawing.channel, CIQ.Drawing.segment); - - CIQ.Drawing.channel.prototype.configs = [ - "color", - "fillColor", - "lineWidth", - "pattern" - ]; - - CIQ.Drawing.channel.prototype.move = function (context, tick, value) { - if (!this.penDown) return; - - this.copyConfig(); - if (this.p2 === null) this.p1 = [tick, value]; - else { - var y = - value - - ((this.p1[1] - this.p0[1]) / (this.p1[0] - this.p0[0])) * - (tick - this.p1[0]); - this.p2 = [this.p1[0], y]; - } - this.render(context); - }; - - CIQ.Drawing.channel.prototype.click = function (context, tick, value) { - var panel = this.stx.panels[this.panelName]; - if (!panel) return; - this.copyConfig(); - if (!this.penDown) { - this.setPoint(0, tick, value, panel.chart); - this.penDown = true; - return false; - } - if (this.accidentalClick(tick, value)) { - this.stx.undo(); //abort - return true; - } - - if (this.p2 !== null) { - this.setPoint(2, this.p2[0], this.p2[1], panel.chart); - this.penDown = false; - return true; - } - this.setPoint(1, tick, value, panel.chart); - if (this.p0[0] == this.p1[0]) { - // don't allow vertical line - this.p1 = null; - return false; - } - this.p2 = [this.p1[0], this.p1[1]]; - return false; - }; - - CIQ.Drawing.channel.prototype.boxIntersection = function (tick, value, box) { - var p0 = this.p0, - p1 = this.p1, - p2 = this.p2; - if (!p0 || !p1 || !p2) return false; - if (box.x0 > Math.max(p0[0], p1[0]) || box.x1 < Math.min(p0[0], p1[0])) - return false; - - // http://stackoverflow.com/questions/1560492/how-to-tell-whether-a-point-is-to-the-right-or-left-side-of-a-line - var s1 = - (p1[0] - p0[0]) * ((p2[1] < p0[1] ? box.y1 : box.y0) - p0[1]) - - (p1[1] - p0[1]) * (tick - p0[0]); - var s2 = - (p2[0] - p0[0]) * - ((p2[1] > p0[1] ? box.y1 : box.y0) - (p0[1] + p2[1] - p1[1])) - - (p1[1] - p0[1]) * (tick - p0[0]); - return s1 * s2 < 0; - }; - - CIQ.Drawing.channel.prototype.intersected = function (tick, value, box) { - if (!this.p0 || !this.p1 || !this.p2) return null; // in case invalid drawing (such as from panel that no longer exists) - var pointsToCheck = { 0: this.p0, 1: this.p1, 2: this.p2 }; - for (var pt in pointsToCheck) { - if ( - this.pointIntersection(pointsToCheck[pt][0], pointsToCheck[pt][1], box) - ) { - this.highlighted = "p" + pt; - return { - action: "drag", - point: "p" + pt - }; - } - } - if (this.boxIntersection(tick, value, box)) { - this.highlighted = true; - // This object will be used for repositioning - return { - action: "move", - p0: CIQ.clone(this.p0), - p1: CIQ.clone(this.p1), - p2: CIQ.clone(this.p2), - tick: tick, // save original tick - value: value // save original value - }; - } - return null; - }; - - CIQ.Drawing.channel.prototype.render = function (context) { - var panel = this.stx.panels[this.panelName]; - if (!panel) return; - var x0 = this.stx.pixelFromTick(this.p0[0], panel.chart); - var x1 = this.stx.pixelFromTick(this.p1[0], panel.chart); - var y0 = this.stx.pixelFromValueAdjusted(panel, this.p0[0], this.p0[1]); - var y1 = this.stx.pixelFromValueAdjusted(panel, this.p1[0], this.p1[1]); - var y = null; - if (this.p2) { - y = this.stx.pixelFromValueAdjusted(panel, this.p2[0], this.p2[1]); - } - - var width = this.lineWidth; - var color = this.getLineColor(); - - var fillColor = this.fillColor; - if ( - this.p2 && - fillColor && - !CIQ.isTransparent(fillColor) && - fillColor != "auto" - ) { - context.beginPath(); - context.moveTo(x0, y0); - context.lineTo(x1, y1); - context.lineTo(x1, y); - context.lineTo(x0, y0 + (y - y1)); - context.closePath(); - context.globalAlpha = 0.2; - context.fillStyle = fillColor; - context.fill(); - context.globalAlpha = 1; - } - - var parameters = { - pattern: this.pattern, - lineWidth: width - }; - if ((this.penDown || this.highlighted) && this.pattern == "none") - parameters.pattern = "dotted"; - this.stx.plotLine( - x0, - x1, - y0, - y1, - color, - "segment", - context, - panel, - parameters - ); - if (this.p2) - this.stx.plotLine( - x0, - x1, - y0 + (y - y1), - y, - color, - "segment", - context, - panel, - parameters - ); - - if (this.highlighted) { - var p0Fill = this.highlighted == "p0" ? true : false; - var p1Fill = this.highlighted == "p1" ? true : false; - var p2Fill = this.highlighted == "p2" ? true : false; - this.littleCircle(context, x0, y0, p0Fill); - this.littleCircle(context, x1, y1, p1Fill); - this.littleCircle(context, x1, y, p2Fill); - } - }; - - CIQ.Drawing.channel.prototype.reposition = function ( - context, - repositioner, - tick, - value - ) { - if (!repositioner) return; - var panel = this.stx.panels[this.panelName]; - var tickDiff = repositioner.tick - tick; - var valueDiff = repositioner.value - value; - if (repositioner.action == "move") { - this.setPoint( - 0, - repositioner.p0[0] - tickDiff, - repositioner.p0[1] - valueDiff, - panel.chart - ); - this.setPoint( - 1, - repositioner.p1[0] - tickDiff, - repositioner.p1[1] - valueDiff, - panel.chart - ); - this.setPoint( - 2, - repositioner.p2[0] - tickDiff, - repositioner.p2[1] - valueDiff, - panel.chart - ); - this.render(context); - } else if (repositioner.action == "drag") { - this[repositioner.point] = [tick, value]; - this.setPoint(0, this.p0[0], this.p0[1], panel.chart); - this.setPoint(1, this.p1[0], this.p1[1], panel.chart); - this.setPoint(2, this.p2[0], this.p2[1], panel.chart); - this.render(context); - } - }; - - CIQ.Drawing.channel.prototype.adjust = function () { - var panel = this.stx.panels[this.panelName]; - if (!panel) return; - this.setPoint(0, this.d0, this.v0, panel.chart); - this.setPoint(1, this.d1, this.v1, panel.chart); - this.setPoint(2, this.d1, this.v2, panel.chart); //not an error, should be d1 here - }; - - /** - * Reconstruct a channel - * @memberOf CIQ.Drawing.channel - * @param {CIQ.ChartEngine} stx The chart object - * @param {object} [obj] A drawing descriptor - * @param {string} [obj.col] The line color - * @param {string} [obj.fc] The fill color - * @param {string} [obj.pnl] The panel name - * @param {string} [obj.ptrn] Pattern for line "solid","dotted","dashed". Defaults to solid. - * @param {number} [obj.lw] Line width. Defaults to 1. - * @param {number} [obj.v0] Value (price) for the first point - * @param {number} [obj.v1] Value (price) for the second point - * @param {number} [obj.v2] Value (price) for the second point of the opposing parallel channel line - * @param {number} [obj.d0] Date (string form) for the first point - * @param {number} [obj.d1] Date (string form) for the second point - * @param {number} [obj.tzo0] Offset of UTC from d0 in minutes - * @param {number} [obj.tzo1] Offset of UTC from d1 in minutes - */ - CIQ.Drawing.channel.prototype.reconstruct = function (stx, obj) { - this.stx = stx; - this.color = obj.col; - this.fillColor = obj.fc; - this.panelName = obj.pnl; - this.pattern = obj.ptrn; - this.lineWidth = obj.lw; - this.d0 = obj.d0; - this.d1 = obj.d1; - this.tzo0 = obj.tzo0; - this.tzo1 = obj.tzo1; - this.v0 = obj.v0; - this.v1 = obj.v1; - this.v2 = obj.v2; - this.adjust(); - }; - - CIQ.Drawing.channel.prototype.serialize = function () { - return { - name: this.name, - pnl: this.panelName, - col: this.color, - fc: this.fillColor, - ptrn: this.pattern, - lw: this.lineWidth, - d0: this.d0, - d1: this.d1, - tzo0: this.tzo0, - tzo1: this.tzo1, - v0: this.v0, - v1: this.v1, - v2: this.v2 - }; - }; - - /** - * Andrews' Pitchfork drawing tool. A Pitchfork is defined by three parallel rays. The center ray is equidistant from the two outer rays. - * - * It inherits its properties from {@link CIQ.Drawing.channel}. - * @constructor - * @name CIQ.Drawing.pitchfork - * @version ChartIQ Advanced Package - */ - CIQ.Drawing.pitchfork = function () { - this.name = "pitchfork"; - this.dragToDraw = false; - this.p2 = null; - }; - - CIQ.inheritsFrom(CIQ.Drawing.pitchfork, CIQ.Drawing.channel); - - CIQ.Drawing.pitchfork.prototype.configs = ["color", "lineWidth", "pattern"]; - - CIQ.Drawing.pitchfork.prototype.move = function (context, tick, value) { - if (!this.penDown) return; - - this.copyConfig(); - if (this.p2 === null) this.p1 = [tick, value]; - else this.p2 = [tick, value]; - this.render(context); - }; - - CIQ.Drawing.pitchfork.prototype.intersected = function (tick, value, box) { - if (!this.p0 || !this.p1 || !this.p2) return null; // in case invalid drawing (such as from panel that no longer exists) - var pointsToCheck = { 0: this.p0, 1: this.p1, 2: this.p2 }; - for (var pt in pointsToCheck) { - if ( - this.pointIntersection(pointsToCheck[pt][0], pointsToCheck[pt][1], box) - ) { - this.highlighted = "p" + pt; - return { - action: "drag", - point: "p" + pt - }; - } - } - var rays = this.rays; - for (var i = 0; i < rays.length; i++) { - if ( - this.lineIntersection( - tick, - value, - box, - i ? "ray" : "segment", - rays[i][0], - rays[i][1], - true - ) - ) { - this.highlighted = true; - // This object will be used for repositioning - return { - action: "move", - p0: CIQ.clone(this.p0), - p1: CIQ.clone(this.p1), - p2: CIQ.clone(this.p2), - tick: tick, // save original tick - value: value // save original value - }; - } - } - return null; - }; - - CIQ.Drawing.pitchfork.prototype.render = function (context) { - var panel = this.stx.panels[this.panelName]; - if (!panel) return; - var stx = this.stx; - var p2 = this.p2; - if (!p2) p2 = this.p1; - var x0 = stx.pixelFromTick(this.p0[0], panel.chart); - var x1 = stx.pixelFromTick(this.p1[0], panel.chart); - var x2 = stx.pixelFromTick(p2[0], panel.chart); - var y0 = stx.pixelFromValueAdjusted(panel, this.p0[0], this.p0[1]); - var y1 = stx.pixelFromValueAdjusted(panel, this.p1[0], this.p1[1]); - var y2 = stx.pixelFromValueAdjusted(panel, p2[0], p2[1]); - - var width = this.lineWidth; - var color = this.getLineColor(); - - var parameters = { - pattern: this.pattern, - lineWidth: width - }; - var z = 50; - var yp = 2 * y0 - y1 - y2; - var denom = 2 * x0 - x1 - x2; - if (denom < 0) z *= -1; - yp *= z / denom; - this.rays = [ - [ - [x1, y1], - [x2, y2] - ], - [ - [x0, y0], - [(x1 + x2) / 2, (y1 + y2) / 2] - ] - ]; - if (!(x1 == x2 && y1 == y2)) { - this.rays.push( - [ - [x1, y1], - [x1 - z, y1 - yp] - ], - [ - [x2, y2], - [x2 - z, y2 - yp] - ] - ); - } - for (var i = 0; i < this.rays.length; i++) { - var ray = this.rays[i], - type = i ? "ray" : "segment"; - stx.plotLine( - ray[0][0], - ray[1][0], - ray[0][1], - ray[1][1], - color, - type, - context, - panel, - parameters - ); - } - if (this.highlighted) { - var p0Fill = this.highlighted == "p0" ? true : false; - var p1Fill = this.highlighted == "p1" ? true : false; - var p2Fill = this.highlighted == "p2" ? true : false; - this.littleCircle(context, x0, y0, p0Fill); - this.littleCircle(context, x1, y1, p1Fill); - this.littleCircle(context, x2, y2, p2Fill); - } - }; - - CIQ.Drawing.pitchfork.prototype.adjust = function () { - var panel = this.stx.panels[this.panelName]; - if (!panel) return; - this.setPoint(0, this.d0, this.v0, panel.chart); - this.setPoint(1, this.d1, this.v1, panel.chart); - this.setPoint(2, this.d2, this.v2, panel.chart); - }; - - /** - * Reconstruct a pitchfork - * @memberOf CIQ.Drawing.pitchfork - * @param {CIQ.ChartEngine} stx The chart object - * @param {object} [obj] A drawing descriptor - * @param {string} [obj.col] The line color - * @param {string} [obj.pnl] The panel name - * @param {string} [obj.ptrn] Pattern for line "solid","dotted","dashed". Defaults to solid. - * @param {number} [obj.lw] Line width. Defaults to 1. - * @param {number} [obj.v0] Value (price) for the first point - * @param {number} [obj.v1] Value (price) for the second point - * @param {number} [obj.v2] Value (price) for the third point - * @param {number} [obj.d0] Date (string form) for the first point - * @param {number} [obj.d1] Date (string form) for the second point - * @param {number} [obj.d2] Date (string form) for the third point - * @param {number} [obj.tzo0] Offset of UTC from d0 in minutes - * @param {number} [obj.tzo1] Offset of UTC from d1 in minutes - * @param {number} [obj.tzo2] Offset of UTC from d2 in minutes - */ - CIQ.Drawing.pitchfork.prototype.reconstruct = function (stx, obj) { - this.stx = stx; - this.color = obj.col; - this.panelName = obj.pnl; - this.pattern = obj.ptrn; - this.lineWidth = obj.lw; - this.d0 = obj.d0; - this.d1 = obj.d1; - this.d2 = obj.d2; - this.tzo0 = obj.tzo0; - this.tzo1 = obj.tzo1; - this.tzo2 = obj.tzo2; - this.v0 = obj.v0; - this.v1 = obj.v1; - this.v2 = obj.v2; - this.adjust(); - }; - - CIQ.Drawing.pitchfork.prototype.serialize = function () { - return { - name: this.name, - pnl: this.panelName, - col: this.color, - ptrn: this.pattern, - lw: this.lineWidth, - d0: this.d0, - d1: this.d1, - d2: this.d2, - tzo0: this.tzo0, - tzo1: this.tzo1, - tzo2: this.tzo2, - v0: this.v0, - v1: this.v1, - v2: this.v2 - }; - }; - - /** - * Gartley drawing tool. Creates a series of four connected line segments, each one completed with a user click. - * Will adhere to Gartley requirements vis-a-vis fibonacci levels etc.. - * - * It inherits its properties from {@link CIQ.Drawing.continuous}. - * @constructor - * @name CIQ.Drawing.gartley - * @version ChartIQ Advanced Package - * @since 04-2015-15 - */ - CIQ.Drawing.gartley = function () { - this.name = "gartley"; - this.dragToDraw = false; - this.maxSegments = 4; - this.shape = null; - this.points = []; - }; - - CIQ.inheritsFrom(CIQ.Drawing.gartley, CIQ.Drawing.continuous); - - CIQ.Drawing.gartley.prototype.check = function (first, second) { - if (!second) return true; - if (first[0] >= second[0] || first[1] == second[1]) return false; - if (this.segment == 1) { - if (first[1] < second[1]) this.shape = "M"; - else this.shape = "W"; - } else if (this.segment == 2) { - if (this.shape == "M" && first[1] < second[1]) return false; - else if (this.shape == "W" && first[1] > second[1]) return false; - else if ((second[1] - first[1]) / (this.points[0][1] - first[1]) < 0.618) - return false; - else if ((second[1] - first[1]) / (this.points[0][1] - first[1]) >= 0.786) - return false; - } else if (this.segment == 3) { - if (this.shape == "M" && first[1] > second[1]) return false; - else if (this.shape == "W" && first[1] < second[1]) return false; - else if ((second[1] - first[1]) / (this.points[1][1] - first[1]) < 0.618) - return false; - else if ((second[1] - first[1]) / (this.points[1][1] - first[1]) >= 0.786) - return false; - } else if (this.segment == 4) { - if ( - this.shape == "M" && - (first[1] < second[1] || second[1] < this.points[0][1]) - ) - return false; - else if ( - this.shape == "W" && - (first[1] > second[1] || second[1] > this.points[0][1]) - ) - return false; - else if ( - (this.points[1][1] - second[1]) / - (this.points[1][1] - this.points[2][1]) < - 1.27 - ) - return false; - else if ( - (this.points[1][1] - second[1]) / - (this.points[1][1] - this.points[2][1]) >= - 1.618 - ) - return false; - } - return true; - }; - - CIQ.Drawing.gartley.prototype.click = function (context, tick, value) { - var panel = this.stx.panels[this.panelName]; - if (!panel) return; - this.copyConfig(); - if (!this.penDown) { - this.setPoint(0, tick, value, panel.chart); - this.pts = []; - this.penDown = true; - this.segment = 1; - return false; - } - if (this.accidentalClick(tick, value)) { - this.penDown = true; - return false; - } - if (this.check(this.p0, this.p1)) { - if (this.segment == 1) this.points.push(this.p0); - this.points.push(this.p1); - this.setPoint(1, tick, value, panel.chart); - this.segment++; - - if (this.segment > this.maxSegments) { - this.setPoint(0, this.points[0][0], this.points[0][1], panel.chart); - this.penDown = false; - return true; - } - this.pts.push(this.d1, this.tzo1, this.v1); - this.setPoint(0, tick, value, panel.chart); // reset initial point for next segment, copy by value - } - return false; - }; - - CIQ.Drawing.gartley.prototype.render = function (context) { - var panel = this.stx.panels[this.panelName]; - if (!panel) return; - var x0 = this.stx.pixelFromTick(this.p0[0], panel.chart); - var x1 = this.stx.pixelFromTick(this.p1[0], panel.chart); - var y0 = this.stx.pixelFromValueAdjusted(panel, this.p0[0], this.p0[1]); - var y1 = this.stx.pixelFromValueAdjusted(panel, this.p1[0], this.p1[1]); - - if (this.segment == 2) { - this.drawDropZone( - context, - 0.618 * this.points[0][1] + 0.382 * this.p0[1], - 0.786 * this.points[0][1] + 0.214 * this.p0[1], - this.p0[0] - ); - } else if (this.segment == 3) { - this.drawDropZone( - context, - 0.618 * this.points[1][1] + 0.382 * this.p0[1], - 0.786 * this.points[1][1] + 0.214 * this.p0[1], - this.p0[0] - ); - } else if (this.segment == 4) { - var bound = 1.618 * this.points[2][1] - 0.618 * this.points[1][1]; - if (this.shape == "M") bound = Math.max(bound, this.points[0][1]); - else bound = Math.min(bound, this.points[0][1]); - this.drawDropZone( - context, - bound, - 1.27 * this.points[2][1] - 0.27 * this.points[1][1], - this.p0[0] - ); - } - - var width = this.lineWidth; - var color = this.getLineColor(); - - var parameters = { - pattern: this.pattern, - lineWidth: width - }; - if ((this.penDown || this.highlighted) && this.pattern == "none") - parameters.pattern = "dotted"; - if (this.segment <= this.maxSegments) - this.stx.plotLine( - x0, - x1, - y0, - y1, - color, - this.name, - context, - panel, - parameters - ); - - var fillColor = this.fillColor; - var coords = []; - if (this.points.length) { - context.beginPath(); - for (var fp = 1; fp < this.points.length && fp <= 4; fp++) { - var xx0 = this.stx.pixelFromTick(this.points[fp - 1][0], panel.chart); - var xx1 = this.stx.pixelFromTick(this.points[fp][0], panel.chart); - var yy0 = this.stx.pixelFromValueAdjusted( - panel, - this.points[fp - 1][0], - this.points[fp - 1][1] - ); - var yy1 = this.stx.pixelFromValueAdjusted( - panel, - this.points[fp][0], - this.points[fp][1] - ); - if (fp == 1) coords.push(xx0, yy0); - coords.push(xx1, yy1); - this.stx.plotLine( - xx0, - xx1, - yy0, - yy1, - color, - this.name, - context, - panel, - parameters - ); - } - if (this.points.length == 2 || this.points.length == 4) { - coords.push(x1, y1); - } - if (this.points[2]) { - coords.push( - this.stx.pixelFromTick(this.points[2][0], panel.chart), - this.stx.pixelFromValueAdjusted( - panel, - this.points[2][0], - this.points[2][1] - ) - ); - } - if (fillColor && fillColor != "auto" && !CIQ.isTransparent(fillColor)) { - for (var c = 0; c < coords.length; c += 2) { - if (c === 0) context.moveTo(coords[0], coords[1]); - context.lineTo(coords[c], coords[c + 1]); - } - context.fillStyle = fillColor; - context.globalAlpha = 0.2; - context.closePath(); - context.fill(); - context.globalAlpha = 1; - } - } - - /*if(this.highlighted){ - var p0Fill=this.highlighted=="p0"?true:false; - var p1Fill=this.highlighted=="p1"?true:false; - this.littleCircle(context, x0, y0, p0Fill); - this.littleCircle(context, x1, y1, p1Fill); - }*/ - }; - - CIQ.Drawing.gartley.prototype.lineIntersection = function ( - tick, - value, - box, - type - ) { - var points = this.points, - panel = this.stx.panels[this.panelName]; - if (points.length != this.maxSegments + 1 || !panel) return false; - for (var pt = 0; pt < points.length - 1; pt++) { - if ( - CIQ.Drawing.BaseTwoPoint.prototype.lineIntersection.call( - this, - tick, - value, - box, - "segment", - points[pt], - points[pt + 1] - ) - ) - return true; - } - return false; - }; - - CIQ.Drawing.gartley.prototype.boxIntersection = function (tick, value, box) { - if (!this.p0 || !this.p1) return false; - if ( - box.x0 > Math.max(this.p0[0], this.p1[0]) || - box.x1 < Math.min(this.p0[0], this.p1[0]) - ) - return false; - var lowPoint = Math.min(this.p0[1], this.p1[1]); - var highPoint = Math.max(this.p0[1], this.p1[1]); - for (var pt = 0; pt < this.points.length; pt++) { - lowPoint = Math.min(lowPoint, this.points[pt][1]); - highPoint = Math.max(highPoint, this.points[pt][1]); - } - if (box.y1 > highPoint || box.y0 < lowPoint) return false; - return true; - }; - - CIQ.Drawing.gartley.prototype.reposition = function ( - context, - repositioner, - tick, - value - ) { - if (!repositioner) return; - var panel = this.stx.panels[this.panelName]; - var tickDiff = repositioner.tick - tick; - repositioner.tick = tick; - var valueDiff = repositioner.value - value; - repositioner.value = value; - if (repositioner.action == "move") { - this.pts = []; - for (var pt = 0; pt < this.points.length; pt++) { - this.points[pt][0] -= tickDiff; - this.points[pt][1] -= valueDiff; - this.setPoint(1, this.points[pt][0], this.points[pt][1], panel.chart); - if (pt && pt < this.points.length - 1) - this.pts.push(this.d1, this.tzo1, this.v1); - this.points[pt] = this.p1; - } - this.setPoint(0, this.points[0][0], this.points[0][1], panel.chart); - this.render(context); - /*}else if(repositioner.action=="drag"){ - this[repositioner.point]=[tick, value]; - this.setPoint(0, this.p0[0], this.p0[1], panel.chart); - this.setPoint(1, this.p1[0], this.p1[1], panel.chart); - this.render(context);*/ - } - }; - - CIQ.Drawing.gartley.prototype.configs = [ - "color", - "fillColor", - "lineWidth", - "pattern" - ]; - - CIQ.Drawing.gartley.prototype.adjust = function () { - // If the drawing's panel doesn't exist then we'll check to see - // whether the panel has been added. If not then there's no way to adjust - var panel = this.stx.panels[this.panelName]; - if (!panel) return; - this.reconstructPoints(); - - this.setPoint(0, this.d0, this.v0, panel.chart); - this.points.unshift(this.p0); - - this.setPoint(1, this.d1, this.v1, panel.chart); - this.points.push(this.p1); - }; - - CIQ.Drawing.gartley.prototype.reconstructPoints = function () { - var panel = this.stx.panels[this.panelName]; - if (!panel) return; - this.points = []; - for (var a = 0; a < this.pts.length; a += 3) { - var d = CIQ.strToDateTime(this.pts[a]); - d.setMinutes( - d.getMinutes() + Number(this.pts[a + 1]) - d.getTimezoneOffset() - ); - this.points.push([ - this.stx.tickFromDate(CIQ.yyyymmddhhmmssmmm(d), panel.chart), - this.pts[a + 2] - ]); - } - }; - - /** - * Reconstruct a gartley - * @memberOf CIQ.Drawing.gartley - * @param {CIQ.ChartEngine} stx The chart object - * @param {object} [obj] A drawing descriptor - * @param {string} [obj.col] The line color - * @param {string} [obj.fc] The fill color - * @param {string} [obj.pnl] The panel name - * @param {string} [obj.ptrn] Pattern for line "solid","dotted","dashed". Defaults to solid. - * @param {number} [obj.lw] Line width. Defaults to 1. - * @param {number} [obj.v0] Value (price) for the first point - * @param {number} [obj.v1] Value (price) for the last point - * @param {number} [obj.d0] Date (string form) for the first point - * @param {number} [obj.d1] Date (string form) for the last point - * @param {number} [obj.tzo0] Offset of UTC from d0 in minutes - * @param {number} [obj.tzo1] Offset of UTC from d1 in minutes - * @param {number} [obj.pts] a serialized list of dates,offsets,values for the 3 intermediate points of the gartley (should be 9 items in list) - */ - CIQ.Drawing.gartley.prototype.reconstruct = function (stx, obj) { - this.stx = stx; - this.color = obj.col; - this.fillColor = obj.fc; - this.panelName = obj.pnl; - this.pattern = obj.ptrn; - this.lineWidth = obj.lw; - this.d0 = obj.d0; - this.d1 = obj.d1; - this.tzo0 = obj.tzo0; - this.tzo1 = obj.tzo1; - this.v0 = obj.v0; - this.v1 = obj.v1; - this.pts = obj.pts.split(","); - this.adjust(); - }; - - CIQ.Drawing.gartley.prototype.serialize = function () { - return { - name: this.name, - pnl: this.panelName, - col: this.color, - fc: this.fillColor, - ptrn: this.pattern, - lw: this.lineWidth, - d0: this.d0, - d1: this.d1, - tzo0: this.tzo0, - tzo1: this.tzo1, - v0: this.v0, - v1: this.v1, - pts: this.pts.join(",") - }; - }; - - /** - * Freeform drawing tool. Set splineTension to a value from 0 to 1 (default .3). This is a dragToDraw function - * and automatically disables the crosshairs while enabled. - * - * It inherits its properties from {@link CIQ.Drawing.segment}. - * @constructor - * @name CIQ.Drawing.freeform - * @version ChartIQ Advanced Package - */ - CIQ.Drawing.freeform = function () { - this.name = "freeform"; - this.splineTension = 0.3; //set to -1 to not use splines at all - this.dragToDraw = true; - }; - - CIQ.inheritsFrom(CIQ.Drawing.freeform, CIQ.Drawing.segment); - - CIQ.Drawing.freeform.prototype.measure = function () {}; - - CIQ.Drawing.freeform.prototype.intersected = function (tick, value, box) { - if (box.x0 > this.hiX || box.x1 < this.lowX) return null; - if (box.y1 > this.hiY || box.y0 < this.lowY) return null; - this.highlighted = true; - // This object will be used for repositioning - return { - action: "move", - p0: CIQ.clone(this.p0), - tick: tick, // save original tick - value: value // save original value - }; - }; - - CIQ.Drawing.freeform.prototype.reposition = function ( - context, - repositioner, - tick, - value - ) { - if (!repositioner) return; - var panel = this.stx.panels[this.panelName]; - var tickDiff = repositioner.tick - tick; - var valueDiff = repositioner.value - value; - if (repositioner.action == "move") { - this.setPoint( - 0, - repositioner.p0[0] - tickDiff, - repositioner.p0[1] - valueDiff, - panel.chart - ); - this.adjust(); - this.render(context); - } - }; - - CIQ.Drawing.freeform.prototype.click = function (context, tick, value) { - var panel = this.stx.panels[this.panelName]; - if (!panel) return; - - if (this.penDown === false) { - this.copyConfig(); - this.startX = Math.round( - this.stx.resolveX(this.stx.pixelFromTick(tick, panel.chart)) - ); - this.startY = Math.round( - this.stx.resolveY(this.stx.pixelFromValueAdjusted(panel, tick, value)) - ); - var d = this.stx.dateFromTick(tick, panel.chart, true); - this.d0 = CIQ.yyyymmddhhmmssmmm(d); - this.tzo0 = d.getTimezoneOffset(); - this.v0 = value; - this.p0 = [ - CIQ.ChartEngine.crosshairX - this.startX, - CIQ.ChartEngine.crosshairY - this.startY - ]; - this.nodes = [this.p0[0], this.p0[1]]; - this.pNodes = [this.p0]; - this.candleWidth = this.stx.layout.candleWidth; - this.multiplier = panel.yAxis.multiplier; - this.interval = this.stx.layout.interval; - this.periodicity = this.stx.layout.periodicity; - this.tempSplineTension = this.splineTension; - this.splineTension = -1; - document.body.style.cursor = "pointer"; - this.penDown = true; - return false; - } - this.penDown = false; - this.splineTension = this.tempSplineTension; - document.body.style.cursor = "auto"; - return true; - }; - - CIQ.Drawing.freeform.prototype.move = function (context, tick, value) { - if (!this.penDown) return; - - var panel = this.stx.panels[this.panelName]; - var d1 = this.stx.dateFromTick(tick, panel.chart, true); - this.d1 = CIQ.yyyymmddhhmmssmmm(d1); - this.tzo1 = d1.getTimezoneOffset(); - this.v1 = value; - this.p1 = [ - CIQ.ChartEngine.crosshairX - this.startX, - panel.yAxis.flipped - ? this.startY - CIQ.ChartEngine.crosshairY - : CIQ.ChartEngine.crosshairY - this.startY - ]; - - if (this.pNodes.length > 2) { - if ( - this.p1[0] == this.pNodes[this.pNodes.length - 2][0] && - this.p1[0] == this.pNodes[this.pNodes.length - 1][0] - ) { - this.pNodes.length--; - this.nodes.length -= 2; - } else if ( - this.p1[1] == this.pNodes[this.pNodes.length - 2][1] && - this.p1[1] == this.pNodes[this.pNodes.length - 1][1] - ) { - this.pNodes.length--; - this.nodes.length -= 2; - } - } - - this.nodes.push(this.p1[0], this.p1[1]); - this.pNodes.push(this.p1); - - this.render(context); - return false; - }; - - //This function does not compute exactly, it uses rough ratios to resize the drawing based on the interval. - CIQ.Drawing.freeform.prototype.intervalRatio = function ( - oldInterval, - newInterval, - oldPeriodicity, - newPeriodicity, - startDate, - symbol - ) { - //approximating functions - function weeksInMonth(startDate, symbol) { - return 5; - } - function daysInWeek(startDate, symbol) { - return 5; - } - function daysInMonth(startDate, symbol) { - return 30; - } - function minPerDay(startDate, symbol) { - if (CIQ.Market.Symbology.isForexSymbol(symbol)) return 1440; - return 390; - } - //1,3,5,10,15,30,"day","week","month" - var returnValue = 0; - if (oldInterval == newInterval) returnValue = 1; - else if (!isNaN(oldInterval) && !isNaN(newInterval)) - returnValue = oldInterval / newInterval; - //two intraday intervals - else if (isNaN(oldInterval)) { - //was daily - if (oldInterval == "month") { - if (newInterval == "week") - returnValue = weeksInMonth(startDate, symbol); - else if (newInterval == "day") - returnValue = daysInMonth(startDate, symbol); - else if (!isNaN(newInterval)) - returnValue = - (daysInMonth(startDate, symbol) * minPerDay(startDate, symbol)) / - newInterval; - } else if (oldInterval == "week") { - if (newInterval == "month") - returnValue = 1 / weeksInMonth(startDate, symbol); - if (newInterval == "day") returnValue = daysInWeek(startDate, symbol); - else if (!isNaN(newInterval)) - returnValue = - (daysInWeek(startDate, symbol) * minPerDay(startDate, symbol)) / - newInterval; - } else if (oldInterval == "day") { - if (newInterval == "week") - returnValue = 1 / daysInWeek(startDate, symbol); - else if (newInterval == "month") - returnValue = 1 / daysInMonth(startDate, symbol); - else if (!isNaN(newInterval)) - returnValue = minPerDay(startDate, symbol) / newInterval; - } - } else if (!isNaN(oldInterval)) { - //switching from intraday to daily - if (newInterval == "month") - returnValue = - oldInterval / - (daysInMonth(startDate, symbol) * minPerDay(startDate, symbol)); - else if (newInterval == "week") - returnValue = - oldInterval / - (daysInWeek(startDate, symbol) * minPerDay(startDate, symbol)); - else if (newInterval == "day") - returnValue = oldInterval / minPerDay(startDate, symbol); - } - returnValue *= oldPeriodicity / newPeriodicity; - return returnValue; - }; - - CIQ.Drawing.freeform.prototype.render = function (context) { - var panel = this.stx.panels[this.panelName]; - if (!panel) return; - - var intvl = this.intervalRatio( - this.interval, - this.stx.layout.interval, - this.periodicity, - this.stx.layout.periodicity, - this.d0, - panel.chart.symbol - ); - if (intvl === 0) return; - - var cwr = this.stx.layout.candleWidth / this.candleWidth; - var mlt = panel.yAxis.multiplier / this.multiplier; - this.setPoint(0, this.d0, this.v0, panel.chart); - var spx = this.stx.pixelFromTick(this.p0[0], panel.chart); - var spy = this.stx.pixelFromValueAdjusted(panel, this.p0[0], this.p0[1]); - var arrPoints = []; - - var width = this.lineWidth; - var color = this.getLineColor(); - - var parameters = { - pattern: this.pattern, - lineWidth: width - }; - - for (var n = 0; n < this.pNodes.length; n++) { - var x0 = intvl * cwr * this.pNodes[n][0] + spx; - var y0 = mlt * this.pNodes[n][1]; - if (panel.yAxis.flipped) y0 = spy - y0; - else y0 += spy; - arrPoints.push(x0, y0); - } - - if (!arrPoints.length) return; - if (this.splineTension < 0) { - this.stx.connectTheDots( - arrPoints, - color, - this.name, - context, - panel, - parameters - ); - } else { - this.stx.plotSpline( - arrPoints, - this.splineTension, - color, - this.name, - context, - true, - parameters - ); - } - }; - - CIQ.Drawing.freeform.prototype.adjust = function () { - // If the drawing's panel doesn't exist then we'll check to see - // whether the panel has been added. If not then there's no way to adjust - var panel = this.stx.panels[this.panelName]; - if (!panel) return; - - var p0 = [this.nodes[0], this.nodes[1]]; - this.pNodes = [p0]; - this.lowX = this.nodes[0]; - this.hiX = this.nodes[0]; - this.lowY = this.nodes[1]; - this.hiY = this.nodes[1]; - - for (var n = 2; n < this.nodes.length; n += 2) { - var p1 = [this.nodes[n], this.nodes[n + 1]]; - this.pNodes.push(p1); - this.lowX = Math.min(this.lowX, p1[0]); - this.hiX = Math.max(this.hiX, p1[0]); - this.lowY = Math.max(this.lowY, p1[1]); //reversed because price axis goes bottom to top - this.hiY = Math.min(this.hiY, p1[1]); - } - - var intvl = this.intervalRatio( - this.interval, - this.stx.layout.interval, - this.periodicity, - this.stx.layout.periodicity, - this.d0, - panel.chart.symbol - ); - if (intvl === 0) return; - - var cwr = this.stx.layout.candleWidth / this.candleWidth; - var mlt = panel.yAxis.multiplier / this.multiplier; - this.setPoint(0, this.d0, this.v0, panel.chart); - var spx = this.stx.pixelFromTick(this.p0[0], panel.chart); - var spy = this.stx.pixelFromValueAdjusted(panel, this.p0[0], this.p0[1]); - - this.lowX = this.stx.tickFromPixel( - Math.floor(intvl * cwr * this.lowX) + spx, - panel.chart - ); - this.hiX = this.stx.tickFromPixel( - Math.ceil(intvl * cwr * this.hiX) + spx, - panel.chart - ); - if (panel.yAxis.flipped) { - this.lowY = this.stx.valueFromPixel( - spy - Math.floor(mlt * this.lowY), - panel - ); - this.hiY = this.stx.valueFromPixel( - spy - Math.ceil(mlt * this.hiY), - panel - ); - } else { - this.lowY = this.stx.valueFromPixel( - Math.floor(mlt * this.lowY) + spy, - panel - ); - this.hiY = this.stx.valueFromPixel( - Math.ceil(mlt * this.hiY) + spy, - panel - ); - } - }; - - CIQ.Drawing.freeform.prototype.serialize = function () { - return { - name: this.name, - pnl: this.panelName, - col: this.color, - ptrn: this.pattern, - lw: this.lineWidth, - cw: Number(this.candleWidth.toFixed(4)), - mlt: Number(this.multiplier.toFixed(4)), - d0: this.d0, - tzo0: this.tzo0, - v0: this.v0, - inter: this.interval, - pd: this.periodicity, - nodes: this.nodes - }; - }; - - /** - * Reconstruct a freeform drawing. It is not recommended to do this programmatically. - * @param {CIQ.ChartEngine} stx The chart object - * @param {object} [obj] A drawing descriptor - * @param {string} [obj.col] The line color - * @param {string} [obj.pnl] The panel name - * @param {string} [obj.ptrn] Pattern for line "solid","dotted","dashed". Defaults to solid. - * @param {number} [obj.lw] Line width. Defaults to 1. - * @param {number} [obj.cw] Candle width from original drawing - * @param {number} [obj.mlt] Y-axis multiplier from original drawing - * @param {number} [obj.v0] Value (price) for the first point - * @param {number} [obj.d0] Date (string form) for the first point - * @param {number} [obj.int] Interval from original drawing - * @param {number} [obj.pd] Periodicity from original drawing - * @param {number} [obj.tzo0] Offset of UTC from d0 in minutes - * @param {array} [obj.nodes] An array of nodes in form [x0a,x0b,y0a,y0b, x1a, x1b, y1a, y1b, ....] - * @memberOf CIQ.Drawing.freeform - */ - CIQ.Drawing.freeform.prototype.reconstruct = function (stx, obj) { - this.stx = stx; - this.color = obj.col; - this.panelName = obj.pnl; - this.pattern = obj.ptrn; - this.lineWidth = obj.lw; - this.candleWidth = obj.cw; - this.multiplier = obj.mlt; - this.d0 = obj.d0; - this.tzo0 = obj.tzo0; - this.v0 = obj.v0; - this.interval = obj.inter; - this.periodicity = obj.pd; - this.nodes = obj.nodes; - this.adjust(); - }; - - /** - * Callout drawing tool. This is like an annotation except it draws a stem and offers a background color and line style. - * - * @constructor - * @name CIQ.Drawing.callout - * @since 2015-11-1 - * @version ChartIQ Advanced Package - * @see {@link CIQ.Drawing.annotation} - */ - CIQ.Drawing.callout = function () { - this.name = "callout"; - this.arr = []; - this.w = 0; - this.h = 0; - this.padding = 4; - this.text = ""; - this.ta = null; - this.fontSize = 0; - this.font = {}; - this.stemEntry = ""; - this.defaultWidth = 50; - this.defaultHeight = 10; - //this.dragToDraw=true; - }; - - CIQ.inheritsFrom(CIQ.Drawing.callout, CIQ.Drawing.annotation); - - CIQ.Drawing.callout.prototype.configs = [ - "color", - "fillColor", - "lineWidth", - "pattern", - "font" - ]; - - CIQ.Drawing.callout.prototype.copyConfig = function (withPreferences) { - CIQ.Drawing.copyConfig(this, withPreferences); - this.borderColor = this.color; - }; - - CIQ.Drawing.callout.prototype.move = function (context, tick, value) { - if (!this.penDown) return; - - this.copyConfig(); - this.p0 = [tick, value]; - this.render(context); - }; - - CIQ.Drawing.callout.prototype.onChange = function (e) { - var panel = this.stx.panels[this.panelName]; - if (!panel) return; - var textarea = e.target; - this.w = textarea.clientWidth; - this.h = textarea.clientHeight; - //textarea.style.left=(this.stx.pixelFromTick(this.p0[0])-this.w/2) + "px"; - //textarea.style.top=(this.stx.pixelFromPrice(this.p0[1],panel)-this.h/2) + "px"; - var context = this.context || this.stx.chart.tempCanvas.context; - CIQ.clearCanvas(context.canvas, this.stx); - this.render(context); - this.edit(context); - }; - - CIQ.Drawing.callout.prototype.render = function (context) { - this.context = context; // remember last context - var panel = this.stx.panels[this.panelName]; - if (!panel) return; - var x0 = this.stx.pixelFromTick(this.p0[0], panel.chart); - var y0 = this.stx.pixelFromValueAdjusted(panel, this.p0[0], this.p0[1]); - if (isNaN(y0)) return; - - context.font = this.fontString; - context.textBaseline = "top"; - var x = x0; - var y = y0; - var w = this.w / 2; - var h = this.h / 2; - if (this.penDown) { - w = this.defaultWidth; - h = this.defaultHeight; - if (!h) h = this.fontSize; - } - var lineWidth = this.lineWidth; - if (!lineWidth) lineWidth = 1.1; - var color = this.color; - if (color == "auto" || CIQ.isTransparent(color)) - color = this.stx.defaultColor; - var borderColor = this.borderColor; - if (borderColor == "auto" || CIQ.isTransparent(borderColor)) - borderColor = this.stx.defaultColor; - if (this.highlighted) - borderColor = this.stx.getCanvasColor("stx_highlight_vector"); - var sx0, sx1, sy0, sy1; - var r = Math.min(Math.min(w, h) / 2, 8); - if (this.stem) { - if (this.stem.t) { - // absolute positioning of stem - sx0 = this.stx.pixelFromTick(this.stem.t); // bottom of stem - sy0 = this.stx.pixelFromValueAdjusted(panel, this.stem.t, this.stem.v); - } else if (this.stem.x) { - // stem with relative offset positioning - sx0 = x; - sy0 = y; - x += this.stem.x; - y += this.stem.y; - } - - var state = ""; - if (sx0 >= x + w) { - sx1 = x + w; - state = "r"; - } // right of text - else if (sx0 > x - w && sx0 < x + w) { - sx1 = x; - state = "c"; - } // center of text - else if (sx0 <= x - w) { - sx1 = x - w; - state = "l"; - } // left of text - - if (sy0 >= y + h) { - sy1 = y + h; - state += "b"; - } // bottom of text - else if (sy0 > y - h && sy0 < y + h) { - sy1 = y; - state += "m"; - } // middle of text - else if (sy0 <= y - h) { - sy1 = y - h; - state += "t"; - } // top of text - - this.stemEntry = state; - - if (state != "cm") { - // make sure stem does not originate underneath the annotation - sx0 = Math.round(sx0); - sx1 = Math.round(sx1); - sy0 = Math.round(sy0); - sy1 = Math.round(sy1); - } - } - if (this.highlighted) { - this.stx.canvasColor("stx_annotation_highlight_bg", context); - } else { - if (this.fillColor) { - context.fillStyle = this.fillColor; - context.globalAlpha = 0.4; - } else if (this.stem) { - // If there's a stem then use the container color otherwise the stem will show through - context.fillStyle = this.stx.containerColor; - } - } - context.strokeStyle = borderColor; - if (context.setLineDash) { - context.setLineDash(CIQ.borderPatternToArray(lineWidth, this.pattern)); - context.lineDashOffset = 0; //start point in array - } - - if (borderColor) { - context.beginPath(); - context.lineWidth = lineWidth; - context.moveTo(x + w - r, y - h); - if (this.stemEntry != "rt") { - context.quadraticCurveTo(x + w, y - h, x + w, y - h + r); //top right - } else { - context.lineTo(sx0, sy0); - context.lineTo(x + w, y - h + r); - } - context.lineTo(x + w, y - r / 2); - if (this.stemEntry == "rm") context.lineTo(sx0, sy0); - context.lineTo(x + w, y + r / 2); - context.lineTo(x + w, y + h - r); - if (this.stemEntry != "rb") { - context.quadraticCurveTo(x + w, y + h, x + w - r, y + h); //bottom right - } else { - context.lineTo(sx0, sy0); - context.lineTo(x + w - r, y + h); - } - context.lineTo(x + r / 2, y + h); - if (this.stemEntry == "cb") context.lineTo(sx0, sy0); - context.lineTo(x - r / 2, y + h); - context.lineTo(x - w + r, y + h); - if (this.stemEntry != "lb") { - context.quadraticCurveTo(x - w, y + h, x - w, y + h - r); //bottom left - } else { - context.lineTo(sx0, sy0); - context.lineTo(x - w, y + h - r); - } - context.lineTo(x - w, y + r / 2); - if (this.stemEntry == "lm") context.lineTo(sx0, sy0); - context.lineTo(x - w, y - r / 2); - context.lineTo(x - w, y - h + r); - if (this.stemEntry != "lt") { - context.quadraticCurveTo(x - w, y - h, x - w + r, y - h); //top left - } else { - context.lineTo(sx0, sy0); - context.lineTo(x - w + r, y - h); - } - context.lineTo(x - r / 2, y - h); - if (this.stemEntry == "ct") context.lineTo(sx0, sy0); - context.lineTo(x + r / 2, y - h); - context.lineTo(x + w - r, y - h); - context.fill(); - context.globalAlpha = 1; - if (this.pattern != "none") context.stroke(); - } - if (this.highlighted) { - this.stx.canvasColor("stx_annotation_highlight", context); - } else { - context.fillStyle = color; - } - y += this.padding; - if (!this.ta) { - for (var i = 0; i < this.arr.length; i++) { - context.fillText(this.arr[i], x - w + this.padding, y - h); - y += this.fontSize; - } - } - context.textBaseline = "alphabetic"; - - if (this.highlighted && !this.noHandles) { - var p0Fill = this.highlighted == "p0" ? true : false; - this.littleCircle(context, sx0, sy0, p0Fill); - } - /*if(this.penDown){ - context.globalAlpha=0.2; - context.fillText("[Your text here]", x-w+this.padding, y-h); - context.globalAlpha=1; - }*/ - }; - - CIQ.Drawing.callout.prototype.click = function (context, tick, value) { - //don't allow user to add callout on the axis. - if (this.stx.overXAxis || this.stx.overYAxis) return; - var panel = this.stx.panels[this.panelName]; - this.copyConfig(); - //this.getFontString(); - this.setPoint(0, tick, value, panel.chart); - if (!this.penDown) { - this.stem = { - d: this.d0, - v: this.v0 - }; - this.penDown = true; - this.adjust(); - return false; - } - this.adjust(); - this.edit(context); - this.penDown = false; - return false; - }; - - CIQ.Drawing.callout.prototype.reposition = function ( - context, - repositioner, - tick, - value - ) { - if (!repositioner) return; - var panel = this.stx.panels[this.panelName]; - var tickDiff = repositioner.tick - tick; - var valueDiff = repositioner.value - value; - if (repositioner.stem) { - if (repositioner.action == "drag") { - this.stem = { - d: this.stx.dateFromTick(tick, panel.chart, true), - v: value - }; - } else if (repositioner.action == "move") { - this.setPoint( - 0, - repositioner.p0[0] - tickDiff, - repositioner.p0[1] - valueDiff, - panel.chart - ); - this.stem = { - d: this.stx.dateFromTick( - this.stx.tickFromDate(repositioner.stem.d, panel.chart) - tickDiff - ), - v: repositioner.stem.v - valueDiff - }; - } - this.adjust(); - } else { - this.setPoint( - 0, - repositioner.p0[0] - tickDiff, - repositioner.p0[1] - valueDiff, - panel.chart - ); - } - this.render(context); - }; - - CIQ.Drawing.callout.prototype.lineIntersection = function ( - tick, - value, - box, - type - ) { - var panel = this.stx.panels[this.panelName]; - var stem = this.stem, - p0 = this.p0, - stx = this.stx; - if (!p0 || !stem || !panel) return false; - var stemTick = stem.t || this.stx.tickFromDate(stem.d, panel.chart); - var pObj = { x0: p0[0], x1: stemTick, y0: p0[1], y1: stem.v }; - var pixelPoint = CIQ.convertBoxToPixels(stx, this.panelName, pObj); - var x0 = pixelPoint.x0; - var y0 = pixelPoint.y0; - var x1 = pixelPoint.x1; - var y1 = pixelPoint.y1; - if (typeof this.stemEntry == "string") { - if (this.stemEntry.indexOf("l") > -1) x0 -= this.w / 2; - else if (this.stemEntry.indexOf("r") > -1) x0 += this.w / 2; - if (this.stemEntry.indexOf("t") > -1) y0 -= this.h / 2; - else if (this.stemEntry.indexOf("b") > -1) y0 += this.h / 2; - } - var pixelBox = CIQ.convertBoxToPixels(stx, this.panelName, box); - return CIQ.boxIntersects( - pixelBox.x0, - pixelBox.y0, - pixelBox.x1, - pixelBox.y1, - x0, - y0, - x1, - y1, - type - ); - }; - - CIQ.Drawing.callout.prototype.intersected = function (tick, value, box) { - var panel = this.stx.panels[this.panelName]; - if (!this.p0) return null; // in case invalid drawing (such as from panel that no longer exists) - if (this.pointIntersection(this.stem.t, this.stem.v, box)) { - this.highlighted = "p0"; - return { - action: "drag", - stem: true - }; - } - var x0 = this.stx.pixelFromTick(this.p0[0], panel.chart) - this.w / 2; - var y0 = - this.stx.pixelFromValueAdjusted(panel, this.p0[0], this.p0[1]) - - this.h / 2; - var x1 = x0 + this.w; - var y1 = y0 + this.h; - if (this.stem && this.stem.x) { - x0 += this.stem.x; - x1 += this.stem.x; - y0 += this.stem.y; - y1 += this.stem.y; - } - var x = this.stx.pixelFromTick(tick, panel.chart); - var y = this.stx.pixelFromValueAdjusted(panel, tick, value); - if ( - x + box.r >= x0 && - x - box.r <= x1 && - y + box.r >= y0 && - y - box.r <= y1 - ) { - this.highlighted = true; - return { - p0: CIQ.clone(this.p0), - tick: tick, - value: value - }; - } - var isIntersected = this.lineIntersection(tick, value, box, "segment"); - if (isIntersected) { - this.highlighted = true; - // This object will be used for repositioning - return { - action: "move", - stem: CIQ.clone(this.stem), - p0: CIQ.clone(this.p0), - tick: tick, // save original tick - value: value // save original value - }; - } - return null; - }; - - /** - * Fibonacci drawing tool. - * - * It inherits its properties from {@link CIQ.Drawing.BaseTwoPoint} - * @constructor - * @name CIQ.Drawing.fibonacci - */ - CIQ.Drawing.fibonacci = function () { - this.name = "fibonacci"; - this.configurator = "fibonacci"; - }; - - CIQ.inheritsFrom(CIQ.Drawing.fibonacci, CIQ.Drawing.BaseTwoPoint); - - CIQ.Drawing.fibonacci.mapping = { - trend: "t", - color: "c", - parameters: "p", - pattern: "pt", - opacity: "o", - lineWidth: "lw", - level: "l", - extendLeft: "e", - printLevels: "pl", - printValues: "pv", - timezone: "tz", - display: "d" - }; - - /** - * Levels to enable by default. - * @memberOf CIQ.Drawing.fibonacci - * @default - * @since 5.2.0 - */ - CIQ.Drawing.fibonacci.prototype.recommendedLevels = [ - -0.618, - -0.382, - 0, - 0.382, - 0.5, - 0.618, - 1, - 1.382, - 1.618 - ]; - - CIQ.Drawing.fibonacci.prototype.configs = [ - "color", - "fillColor", - "lineWidth", - "pattern", - "parameters" - ]; - - /** - * Set the default fib settings for the type of fib tool selected. References {@link CIQ.Drawing.fibonacci#recommendedLevels}. - * @param {CIQ.ChartEngine} stx Chart object - * @memberOf CIQ.Drawing.fibonacci - * @since 5.2.0 - */ - CIQ.Drawing.fibonacci.prototype.initializeSettings = function (stx) { - var recommendedLevels = this.recommendedLevels; - if ( - recommendedLevels && - !stx.currentVectorParameters.fibonacci.fibsAlreadySet - ) { - var fibs = stx.currentVectorParameters.fibonacci.fibs; - for (var index = 0; index < fibs.length; index++) { - delete fibs[index].display; - for (var rIndex = 0; rIndex < recommendedLevels.length; rIndex++) { - if (fibs[index].level == recommendedLevels[rIndex]) - fibs[index].display = true; - } - } - } - }; - - /* - * Calculate the outer points of the fib series, which are used to detect highlighting - */ - CIQ.Drawing.fibonacci.prototype.setOuter = function () { - var stx = this.stx, - panel = stx.panels[this.panelName]; - if (!panel) return; - var max = Math.max(this.p0[1], this.p1[1]); - var min = Math.min(this.p0[1], this.p1[1]); - var dist = max - min; - - this.outer = { - p0: CIQ.clone(this.p0), - p1: CIQ.clone(this.p1) - }; - var y0 = stx.pixelFromValueAdjusted(panel, this.p0[0], this.p0[1]); - var y1 = stx.pixelFromValueAdjusted(panel, this.p1[0], this.p1[1]); - var x0 = stx.pixelFromTick(this.p0[0], panel.chart); - var x1 = stx.pixelFromTick(this.p1[0], panel.chart); - - var minFib = 0; - var maxFib = 1; - for (var i = 0; i < this.parameters.fibs.length; i++) { - var fib = this.parameters.fibs[i]; - if ((fib.level >= minFib && fib.level <= maxFib) || !fib.display) - continue; - var y = stx.pixelFromValueAdjusted( - panel, - this.p0[0], - y1 < y0 ? max - dist * fib.level : min + dist * fib.level - ); - var x = CIQ.xIntersection({ x0: x0, x1: x1, y0: y0, y1: y1 }, y); - if (fib.level < minFib) { - minFib = fib.level; - this.outer.p1[1] = stx.valueFromPixel(y, panel); - this.outer.p1[0] = stx.tickFromPixel(x, panel.chart); - } else if (fib.level > maxFib) { - maxFib = fib.level; - this.outer.p0[1] = stx.valueFromPixel(y, panel); - this.outer.p0[0] = stx.tickFromPixel(x, panel.chart); - } - } - }; - - CIQ.Drawing.fibonacci.prototype.click = function (context, tick, value) { - var panel = this.stx.panels[this.panelName]; - if (!panel) return; - this.copyConfig(); - if (!this.penDown) { - this.setPoint(0, tick, value, panel.chart); - this.penDown = true; - return false; - } - if (this.accidentalClick(tick, value)) return this.dragToDraw; - - this.setPoint(1, tick, value, panel.chart); - this.setOuter(); - this.parameters = CIQ.clone(this.parameters); // separate from the global object - this.penDown = false; - - return true; // kernel will call render after this - }; - - CIQ.Drawing.fibonacci.prototype.render = function (context) { - var panel = this.stx.panels[this.panelName]; - if (!panel) return; - var yAxis = panel.yAxis; - if (!this.p1) return; - var max = Math.max(this.p0[1], this.p1[1]); - var min = Math.min(this.p0[1], this.p1[1]); - var dist = yAxis.flipped ? min - max : max - min; - var x0 = this.stx.pixelFromTick(this.p0[0], panel.chart); - var x1 = this.stx.pixelFromTick(this.p1[0], panel.chart); - var y0 = this.stx.pixelFromValueAdjusted(panel, this.p0[0], this.p0[1]); - var y1 = this.stx.pixelFromValueAdjusted(panel, this.p1[0], this.p1[1]); - var top = Math.min(y1, y0); - var bottom = Math.max(y1, y0); - var height = bottom - top; - var isUpTrend = (y1 - y0) / (x1 - x0) > 0; - - //old drawings missing parameters.trend - var trend = { - color: "auto", - parameters: { pattern: "solid", opacity: 0.25, lineWidth: 1 } - }; - if (!this.parameters.trend) this.parameters.trend = trend; - var trendLineColor = this.getLineColor(this.parameters.trend.color); - context.textBaseline = "middle"; - this.stx.canvasFont("stx_yaxis", context); // match font from y axis so it looks cohesive - var w = context.measureText("161.8%").width + 10; // give it extra space so it does not overlap with the price labels. - var minX = Number.MAX_VALUE, - minY = Number.MAX_VALUE, - maxX = Number.MAX_VALUE * -1, - maxY = Number.MAX_VALUE * -1; - var txtColor = this.color; - if (txtColor == "auto" || CIQ.isTransparent(txtColor)) - txtColor = this.stx.defaultColor; - this.rays = []; - for (var i = 0; i < this.parameters.fibs.length; i++) { - context.textAlign = "left"; - context.fillStyle = txtColor; - var fib = this.parameters.fibs[i]; - if (!fib.display) continue; - var y = this.stx.pixelFromValueAdjusted( - panel, - this.p0[0], - y1 < y0 ? max - dist * fib.level : min + dist * fib.level - ); - var x = CIQ.xIntersection({ x0: x0, x1: x1, y0: y0, y1: y1 }, y); - var nearX = this.parameters.extendLeft ? 0 : x; - var farX = panel.left + panel.width; - if (this.parameters.printLevels) { - var txt = Math.round(fib.level * 1000) / 10 + "%"; - farX -= w; - if (this.parameters.printValues) { - context.fillStyle = txtColor; // the price labels screw up the color and font size...so reset before rendering the text - this.stx.canvasFont("stx_yaxis", context); // use the same context as the y axis so they match. - } - if (farX < nearX) context.textAlign = "right"; - context.fillText(txt, farX, y); - if (farX < nearX) farX += 5; - else farX -= 5; - } - if (this.parameters.printValues) { - if (x < panel.width) { - // just use the actual price that segment will render on regardless of 'isUpTrend' since the values must match the prices on the y axis, and can not be reversed. - var price = this.stx.transformedPriceFromPixel(y, panel); - if (yAxis.priceFormatter) { - price = yAxis.priceFormatter(this.stx, panel, price); - } else { - price = this.stx.formatYAxisPrice(price, panel); - } - if (context == this.stx.chart.context) this.stx.endClip(); - this.stx.createYAxisLabel(panel, price, y, txtColor, null, context); - if (context == this.stx.chart.context) this.stx.startClip(panel.name); - } - } - var fibColor = fib.color; - if (fibColor == "auto" || CIQ.isTransparent(fibColor)) - fibColor = this.color; - if (fibColor == "auto" || CIQ.isTransparent(fibColor)) - fibColor = this.stx.defaultColor; - var fillColor = fib.color; - if (fillColor == "auto" || CIQ.isTransparent(fillColor)) - fillColor = this.fillColor; - if (fillColor == "auto" || CIQ.isTransparent(fillColor)) - fillColor = this.stx.defaultColor; - context.fillStyle = fillColor; - var fibParameters = CIQ.clone(fib.parameters); - if (this.highlighted) fibParameters.opacity = 1; - this.stx.plotLine( - nearX, - farX, - y, - y, - this.highlighted ? trendLineColor : fibColor, - "segment", - context, - panel, - fibParameters - ); - this.rays.push([ - [nearX, y], - [farX, y] - ]); - context.globalAlpha = 0.05; - context.beginPath(); - context.moveTo(farX, y); - context.lineTo(nearX, y); - if (nearX) context.lineTo(x1, y1); - else context.lineTo(nearX, y1); - context.lineTo(farX, y1); - if (typeof fillColor != "undefined") context.fill(); // so legacy fibs continue to have no fill color. - context.globalAlpha = 1; - if (y < minY) { - minX = x; - minY = y; - } - if (y > maxY) { - maxX = x; - maxY = y; - } - } - // ensure we at least draw trend line from zero to 100 - for (var level = 0; level <= 1; level++) { - var yy = isUpTrend ? bottom - height * level : top + height * level; - yy = Math.round(yy); - if (yy < minY) { - minX = CIQ.xIntersection({ x0: x0, x1: x1, y0: y0, y1: y1 }, yy); - minY = yy; - } - if (yy > maxY) { - maxX = CIQ.xIntersection({ x0: x0, x1: x1, y0: y0, y1: y1 }, yy); - maxY = yy; - } - } - var trendParameters = CIQ.clone(this.parameters.trend.parameters); - if (this.highlighted) trendParameters.opacity = 1; - this.stx.plotLine( - minX, - maxX, - minY, - maxY, - trendLineColor, - "segment", - context, - panel, - trendParameters - ); - if (this.highlighted) { - var p0Fill = this.highlighted == "p0" ? true : false; - var p1Fill = this.highlighted == "p1" ? true : false; - this.littleCircle(context, x0, y0, p0Fill); - this.littleCircle(context, x1, y1, p1Fill); - } - }; - - CIQ.Drawing.fibonacci.prototype.reposition = function ( - context, - repositioner, - tick, - value - ) { - if (!repositioner) return; - CIQ.Drawing.BaseTwoPoint.prototype.reposition.apply(this, arguments); - this.adjust(); - }; - - CIQ.Drawing.fibonacci.prototype.intersected = function (tick, value, box) { - var p0 = this.p0, - p1 = this.p1; - if (!p0 || !p1) return null; // in case invalid drawing (such as from panel that no longer exists) - var pointsToCheck = { 0: p0, 1: p1 }; - for (var pt in pointsToCheck) { - if ( - this.pointIntersection(pointsToCheck[pt][0], pointsToCheck[pt][1], box) - ) { - this.highlighted = "p" + pt; - return { - action: "drag", - point: "p" + pt - }; - } - } - var outer = this.outer, - rays = this.rays; - var isIntersected = - outer && - this.lineIntersection(tick, value, box, "segment", outer.p0, outer.p1); - if (!isIntersected) { - for (var i = 0; i < rays.length; i++) { - if ( - this.lineIntersection( - tick, - value, - box, - "ray", - rays[i][0], - rays[i][1], - true - ) - ) { - isIntersected = true; - break; - } - } - } - if (isIntersected) { - this.highlighted = true; - // This object will be used for repositioning - return { - action: "move", - p0: CIQ.clone(p0), - p1: CIQ.clone(p1), - tick: tick, // save original tick - value: value // save original value - }; - } - return null; - }; - - /** - * Reconstruct a fibonacci - * @param {CIQ.ChartEngine} stx The chart object - * @param {object} [obj] A drawing descriptor - * @param {string} [obj.col] The border color - * @param {string} [obj.fc] The fill color - * @param {string} [obj.pnl] The panel name - * @param {number} [obj.v0] Value (price) for the first point - * @param {number} [obj.v1] Value (price) for the second point - * @param {number} [obj.v2] Value (price) for the third point (if used) - * @param {number} [obj.d0] Date (string form) for the first point - * @param {number} [obj.d1] Date (string form) for the second point - * @param {number} [obj.d2] Date (string form) for the third point (if used) - * @param {number} [obj.tzo0] Offset of UTC from d0 in minutes - * @param {number} [obj.tzo1] Offset of UTC from d1 in minutes - * @param {number} [obj.tzo2] Offset of UTC from d2 in minutes (if used) - * @param {object} [obj.parameters] Configuration parameters - * @param {object} [obj.parameters.trend] Describes the trend line - * @param {string} [obj.parameters.trend.color] The color for the trend line (Defaults to "auto") - * @param {object} [obj.parameters.trend.parameters] Line description object (pattern, opacity, lineWidth) - * @param {array} [obj.parameters.fibs] A fib description object for each fib (level, color, parameters, display) - * @param {boolean} [obj.parameters.extendLeft] True to extend the fib lines to the left of the screen. Defaults to false. - * @param {boolean} [obj.parameters.printLevels] True (default) to print text for each percentage level - * @param {boolean} [obj.parameters.printValues] True to print text for each price level - * @memberOf CIQ.Drawing.fibonacci - */ - CIQ.Drawing.fibonacci.prototype.reconstruct = function (stx, obj) { - obj = CIQ.replaceFields( - obj, - CIQ.reverseObject(CIQ.Drawing.fibonacci.mapping) - ); - this.stx = stx; - this.parameters = obj.parameters; - if (!this.parameters) - this.parameters = CIQ.clone(this.stx.currentVectorParameters.fibonacci); // For legacy fibs that didn't include parameters - this.color = obj.col; - this.fillColor = obj.fc; - this.panelName = obj.pnl; - this.d0 = obj.d0; - this.d1 = obj.d1; - this.d2 = obj.d2; - this.tzo0 = obj.tzo0; - this.tzo1 = obj.tzo1; - this.tzo2 = obj.tzo2; - this.v0 = obj.v0; - this.v1 = obj.v1; - this.v2 = obj.v2; - this.adjust(); - }; - - CIQ.Drawing.fibonacci.prototype.adjust = function () { - var panel = this.stx.panels[this.panelName]; - if (!panel) return; - this.setPoint(0, this.d0, this.v0, panel.chart); - this.setPoint(1, this.d1, this.v1, panel.chart); - this.setOuter(); - }; - - CIQ.Drawing.fibonacci.prototype.serialize = function () { - var obj = { - name: this.name, - parameters: this.parameters, - pnl: this.panelName, - col: this.color, - fc: this.fillColor, - d0: this.d0, - d1: this.d1, - d2: this.d2, - tzo0: this.tzo0, - tzo1: this.tzo1, - tzo2: this.tzo2, - v0: this.v0, - v1: this.v1, - v2: this.v2 - }; - return CIQ.replaceFields(obj, CIQ.Drawing.fibonacci.mapping); - }; - - /** - * Retracement drawing tool. - * - * It inherits its properties from {@link CIQ.Drawing.fibonacci} - * @constructor - * @name CIQ.Drawing.retracement - */ - CIQ.Drawing.retracement = function () { - this.name = "retracement"; - }; - - CIQ.inheritsFrom(CIQ.Drawing.retracement, CIQ.Drawing.fibonacci); - - /** - * Fibonacci projection drawing tool. - * - * It inherits its properties from {@link CIQ.Drawing.fibonacci} - * @constructor - * @name CIQ.Drawing.fibprojection - * @version ChartIQ Advanced Package - * @since 5.2.0 - */ - CIQ.Drawing.fibprojection = function () { - this.name = "fibprojection"; - this.dragToDraw = false; - this.p2 = null; - }; - - CIQ.inheritsFrom(CIQ.Drawing.fibprojection, CIQ.Drawing.fibonacci); - - CIQ.Drawing.fibprojection.prototype.recommendedLevels = [ - 0, - 0.618, - 1, - 1.272, - 1.618, - 2.618, - 4.236 - ]; - - CIQ.Drawing.fibprojection.prototype.click = function (context, tick, value) { - var panel = this.stx.panels[this.panelName]; - if (!panel) return; - this.copyConfig(); - if (!this.penDown) { - this.setPoint(0, tick, value, panel.chart); - this.penDown = true; - return false; - } - if (this.accidentalClick(tick, value)) { - this.stx.undo(); //abort - return true; - } - - if (this.p2 !== null) { - this.setPoint(2, this.p2[0], this.p2[1], panel.chart); - this.parameters = CIQ.clone(this.parameters); // separate from the global object - return true; - } - this.setPoint(1, tick, value, panel.chart); - - this.p2 = [this.p1[0], this.p1[1]]; - return false; // kernel will call render after this - }; - - CIQ.Drawing.fibprojection.prototype.render = function (context) { - var panel = this.stx.panels[this.panelName]; - if (!panel) return; - var yAxis = panel.yAxis; - if (!this.p1) return; - var dist = this.p1[1] - this.p0[1]; - var x0 = this.stx.pixelFromTick(this.p0[0], panel.chart); - var x1 = this.stx.pixelFromTick(this.p1[0], panel.chart); - var y0 = this.stx.pixelFromValueAdjusted(panel, this.p0[0], this.p0[1]); - var y1 = this.stx.pixelFromValueAdjusted(panel, this.p1[0], this.p1[1]); - var x2 = null, - y2 = null; - if (this.p2) { - x2 = this.stx.pixelFromTick(this.p2[0], panel.chart); - y2 = this.stx.pixelFromValueAdjusted(panel, this.p2[0], this.p2[1]); - } - //old drawings missing parameters.trend - var trend = { - color: "auto", - parameters: { pattern: "solid", opacity: 0.25, lineWidth: 1 } - }; - if (!this.parameters.trend) this.parameters.trend = trend; - var trendLineColor = this.getLineColor(this.parameters.trend.color); - context.textBaseline = "middle"; - this.stx.canvasFont("stx_yaxis", context); // match font from y axis so it looks cohesive - var w = context.measureText("161.8%").width + 10; // give it extra space so it does not overlap with the price labels. - var txtColor = this.color; - if (txtColor == "auto" || CIQ.isTransparent(txtColor)) - txtColor = this.stx.defaultColor; - if (this.p2) { - this.rays = []; - for (var i = 0; i < this.parameters.fibs.length; i++) { - context.textAlign = "left"; - context.fillStyle = txtColor; - var fib = this.parameters.fibs[i]; - if (!fib.display) continue; - var y = this.stx.pixelFromValueAdjusted( - panel, - this.p2[0], - this.p2[1] + dist * fib.level - ); - var x = CIQ.xIntersection({ x0: x0, x1: x1, y0: y0, y1: y1 }, y); - var nearX = this.parameters.extendLeft ? 0 : x0; - var farX = panel.left + panel.width; - if (this.parameters.printLevels) { - var txt = Math.round(fib.level * 1000) / 10 + "%"; - farX -= w; - if (this.parameters.printValues) { - context.fillStyle = txtColor; // the price labels screw up the color and font size...so reset before rendering the text - this.stx.canvasFont("stx_yaxis", context); // use the same context as the y axis so they match. - } - if (farX < nearX) context.textAlign = "right"; - context.fillText(txt, farX, y); - if (farX < nearX) farX += 5; - else farX -= 5; - } - if (this.parameters.printValues) { - if (x < panel.width) { - // just use the actual price that segment will render on regardless of 'isUpTrend' since the values must match the prices on the y axis, and can not be reversed. - var price = this.stx.transformedPriceFromPixel(y, panel); - if (yAxis.priceFormatter) { - price = yAxis.priceFormatter(this.stx, panel, price); - } else { - price = this.stx.formatYAxisPrice(price, panel); - } - if (context == this.stx.chart.context) this.stx.endClip(); - this.stx.createYAxisLabel(panel, price, y, txtColor, null, context); - if (context == this.stx.chart.context) - this.stx.startClip(panel.name); - } - } - var fibColor = fib.color; - if (fibColor == "auto" || CIQ.isTransparent(fibColor)) - fibColor = this.color; - if (fibColor == "auto" || CIQ.isTransparent(fibColor)) - fibColor = this.stx.defaultColor; - var fillColor = fib.color; - if (fillColor == "auto" || CIQ.isTransparent(fillColor)) - fillColor = this.fillColor; - if (fillColor == "auto" || CIQ.isTransparent(fillColor)) - fillColor = this.stx.defaultColor; - context.fillStyle = fillColor; - var fibParameters = CIQ.clone(fib.parameters); - if (this.highlighted) fibParameters.opacity = 1; - this.stx.plotLine( - nearX, - farX, - y, - y, - this.highlighted ? trendLineColor : fibColor, - "segment", - context, - panel, - fibParameters - ); - this.rays.push([ - [nearX, y], - [farX, y] - ]); - context.globalAlpha = 0.05; - context.beginPath(); - context.moveTo(farX, y); - context.lineTo(nearX, y); - if (nearX) context.lineTo(x0, y2); - else context.lineTo(nearX, y2); - context.lineTo(farX, y2); - if (typeof fillColor != "undefined") context.fill(); // so legacy fibs continue to have no fill color. - context.globalAlpha = 1; - } - } - var trendParameters = CIQ.clone(this.parameters.trend.parameters); - if (this.highlighted) trendParameters.opacity = 1; - this.stx.plotLine( - x0, - x1, - y0, - y1, - trendLineColor, - "segment", - context, - panel, - trendParameters - ); - if (this.p2) - this.stx.plotLine( - x1, - x2, - y1, - y2, - trendLineColor, - "segment", - context, - panel, - trendParameters - ); - if (this.highlighted) { - var p0Fill = this.highlighted == "p0" ? true : false; - var p1Fill = this.highlighted == "p1" ? true : false; - var p2Fill = this.highlighted == "p2" ? true : false; - this.littleCircle(context, x0, y0, p0Fill); - this.littleCircle(context, x1, y1, p1Fill); - this.littleCircle(context, x2, y2, p2Fill); - } - }; - - CIQ.Drawing.fibprojection.prototype.move = function (context, tick, value) { - if (!this.penDown) return; - this.copyConfig(); - if (this.p2 === null) this.p1 = [tick, value]; - else this.p2 = [tick, value]; - this.render(context); - }; - - CIQ.Drawing.fibprojection.prototype.reposition = function ( - context, - repositioner, - tick, - value - ) { - if (!repositioner) return; - var panel = this.stx.panels[this.panelName]; - var tickDiff = repositioner.tick - tick; - var valueDiff = repositioner.value - value; - if (repositioner.action == "move") { - this.setPoint( - 0, - repositioner.p0[0] - tickDiff, - repositioner.p0[1] - valueDiff, - panel.chart - ); - this.setPoint( - 1, - repositioner.p1[0] - tickDiff, - repositioner.p1[1] - valueDiff, - panel.chart - ); - this.setPoint( - 2, - repositioner.p2[0] - tickDiff, - repositioner.p2[1] - valueDiff, - panel.chart - ); - this.render(context); - } else if (repositioner.action == "drag") { - this[repositioner.point] = [tick, value]; - this.setPoint(0, this.p0[0], this.p0[1], panel.chart); - this.setPoint(1, this.p1[0], this.p1[1], panel.chart); - this.setPoint(2, this.p2[0], this.p2[1], panel.chart); - this.render(context); - } - }; - - CIQ.Drawing.fibprojection.prototype.intersected = function ( - tick, - value, - box - ) { - var p0 = this.p0, - p1 = this.p1, - p2 = this.p2; - if (!p0 || !p1 || !p2) return null; // in case invalid drawing (such as from panel that no longer exists) - var pointsToCheck = { 0: p0, 1: p1, 2: p2 }; - for (var pt in pointsToCheck) { - if ( - this.pointIntersection(pointsToCheck[pt][0], pointsToCheck[pt][1], box) - ) { - this.highlighted = "p" + pt; - return { - action: "drag", - point: "p" + pt - }; - } - } - var rays = this.rays; - var isIntersected = - this.lineIntersection(tick, value, box, "segment", p0, p1) || - this.lineIntersection(tick, value, box, "segment", p1, p2); - if (!isIntersected) { - for (var i = 0; i < rays.length; i++) { - if ( - this.lineIntersection( - tick, - value, - box, - "ray", - rays[i][0], - rays[i][1], - true - ) - ) { - isIntersected = true; - break; - } - } - } - if (isIntersected) { - this.highlighted = true; - // This object will be used for repositioning - return { - action: "move", - p0: CIQ.clone(p0), - p1: CIQ.clone(p1), - p2: CIQ.clone(p2), - tick: tick, // save original tick - value: value // save original value - }; - } - return null; - }; - - CIQ.Drawing.fibprojection.prototype.adjust = function () { - var panel = this.stx.panels[this.panelName]; - if (!panel) return; - this.setPoint(0, this.d0, this.v0, panel.chart); - this.setPoint(1, this.d1, this.v1, panel.chart); - this.setPoint(2, this.d2, this.v2, panel.chart); - }; - - /** - * Fibonacci Arc drawing tool. - * - * It inherits its properties from {@link CIQ.Drawing.fibonacci} - * @constructor - * @name CIQ.Drawing.fibarc - * @since 2015-11-1 - * @version ChartIQ Advanced Package - */ - CIQ.Drawing.fibarc = function () { - this.name = "fibarc"; - //this.dragToDraw=true; - }; - - CIQ.inheritsFrom(CIQ.Drawing.fibarc, CIQ.Drawing.fibonacci); - - CIQ.Drawing.fibarc.prototype.recommendedLevels = [0.382, 0.5, 0.618, 1]; - - CIQ.Drawing.fibarc.prototype.setOuter = function () { - var panel = this.stx.panels[this.panelName]; - if (!panel) return; - - this.outer = { - p0: CIQ.clone(this.p0), - p1: CIQ.clone(this.p1) - }; - var y0 = this.stx.pixelFromValueAdjusted(panel, this.p0[0], this.p0[1]); - var y1 = this.stx.pixelFromValueAdjusted(panel, this.p1[0], this.p1[1]); - var x0 = this.stx.pixelFromTick(this.p0[0], panel.chart); - var x1 = this.stx.pixelFromTick(this.p1[0], panel.chart); - var y = 2 * y0 - y1; - var x = CIQ.xIntersection({ x0: x0, x1: x1, y0: y0, y1: y1 }, y); - this.outer.p0[1] = this.stx.valueFromPixel(y, panel); - this.outer.p0[0] = this.stx.tickFromPixel(x, panel.chart); - }; - - CIQ.Drawing.fibarc.prototype.intersected = function (tick, value, box) { - var panel = this.stx.panels[this.panelName]; - if (!panel) return; - var p0 = this.p0, - p1 = this.p1, - outer = this.outer; - if (!p0 || !p1) return null; // in case invalid drawing (such as from panel that no longer exists) - var pointsToCheck = { 0: p0, 1: p1 }; - for (var pt in pointsToCheck) { - if ( - this.pointIntersection(pointsToCheck[pt][0], pointsToCheck[pt][1], box) - ) { - this.highlighted = "p" + pt; - return { - action: "drag", - point: "p" + pt - }; - } - } - if ( - this.lineIntersection(tick, value, box, "segment", outer.p0, outer.p1) - ) { - this.highlighted = true; - // This object will be used for repositioning - return { - action: "move", - p0: CIQ.clone(p0), - p1: CIQ.clone(p1), - tick: tick, // save original tick - value: value // save original value - }; - } - // Just test the box circumscribing the arcs - var points = { x0: p0[0], x1: p1[0], y0: p0[1], y1: p1[1] }; - var pixelArea = CIQ.convertBoxToPixels(this.stx, this.panelName, points); - var extend = { - x: Math.abs(Math.sqrt(2) * (pixelArea.x1 - pixelArea.x0)), - y: Math.abs(Math.sqrt(2) * (pixelArea.y1 - pixelArea.y0)) - }; - var x = this.stx.pixelFromTick(tick, panel.chart); - var y = this.stx.pixelFromValueAdjusted(panel, tick, value); - - if ( - x + box.r < pixelArea.x1 - extend.x || - x - box.r > pixelArea.x1 + extend.x - ) - return null; - if ( - y + box.r < pixelArea.y1 - extend.y || - y - box.r > pixelArea.y1 + extend.y - ) - return null; - if (pixelArea.y0 < pixelArea.y1 && y - box.r > pixelArea.y1) return null; - if (pixelArea.y0 > pixelArea.y1 && y + box.r < pixelArea.y1) return null; - this.highlighted = true; - return { - action: "move", - p0: CIQ.clone(this.p0), - p1: CIQ.clone(this.p1), - tick: tick, - value: value - }; - }; - - CIQ.Drawing.fibarc.prototype.render = function (context) { - var panel = this.stx.panels[this.panelName]; - if (!panel) return; - var yAxis = panel.yAxis; - if (!this.p1) return; - var x0 = this.stx.pixelFromTick(this.p0[0], panel.chart); - var x1 = this.stx.pixelFromTick(this.p1[0], panel.chart); - var y0 = this.stx.pixelFromValueAdjusted(panel, this.p0[0], this.p0[1]); - var y1 = this.stx.pixelFromValueAdjusted(panel, this.p1[0], this.p1[1]); - var isUpTrend = y1 < y0; - var factor = Math.abs((y1 - y0) / (x1 - x0)); - - var trendLineColor = this.getLineColor(this.parameters.trend.color); - context.textBaseline = "middle"; - this.stx.canvasFont("stx_yaxis", context); // match font from y axis so it looks cohesive - var txtColor = this.color; - if (txtColor == "auto" || CIQ.isTransparent(txtColor)) - txtColor = this.stx.defaultColor; - for (var i = 0; i < this.parameters.fibs.length; i++) { - context.fillStyle = txtColor; - var fib = this.parameters.fibs[i]; - if (fib.level < 0 || !fib.display) continue; - var radius = Math.abs(this.p1[1] - this.p0[1]) * Math.sqrt(2) * fib.level; - var value = - this.p1[1] + radius * (isUpTrend ? -1 : 1) * (yAxis.flipped ? -1 : 1); - var y = this.stx.pixelFromValueAdjusted(panel, this.p0[0], value); - var x = CIQ.xIntersection({ x0: x0, x1: x1, y0: y0, y1: y1 }, y); - if (this.parameters.printLevels) { - context.textAlign = "center"; - var txt = Math.round(fib.level * 1000) / 10 + "%"; - if (this.parameters.printValues) { - context.fillStyle = txtColor; // the price labels screw up the color and font size...so reset before rendering the text - this.stx.canvasFont("stx_yaxis", context); // use the same context as the y axis so they match. - } - context.fillText(txt, x1, Math.round(y - 5)); - } - context.textAlign = "left"; - if (this.parameters.printValues) { - if (x < panel.width) { - // just use the actual price that segment will render on regardless of 'isUpTrend' since the values must match the prices on the y axis, and can not be reversed. - var price = value; - if (yAxis.priceFormatter) { - price = yAxis.priceFormatter(this.stx, panel, price); - } else { - price = this.stx.formatYAxisPrice(price, panel); - } - if (context == this.stx.chart.context) this.stx.endClip(); - this.stx.createYAxisLabel(panel, price, y, txtColor, null, context); - if (context == this.stx.chart.context) this.stx.startClip(panel.name); - } - } - var fibColor = fib.color; - if (fibColor == "auto" || CIQ.isTransparent(fibColor)) - fibColor = this.color; - if (fibColor == "auto" || CIQ.isTransparent(fibColor)) - fibColor = this.stx.defaultColor; - context.strokeStyle = this.highlight ? trendLineColor : fibColor; - var fillColor = fib.color; - if (fillColor == "auto" || CIQ.isTransparent(fillColor)) - fillColor = this.fillColor; - if (fillColor == "auto" || CIQ.isTransparent(fillColor)) - fillColor = this.stx.defaultColor; - context.fillStyle = fillColor; - context.globalAlpha = this.highlighted ? 1 : fib.parameters.opacity; - context.lineWidth = fib.parameters.lineWidth; - if (context.setLineDash) { - context.setLineDash( - CIQ.borderPatternToArray(context.lineWidth, fib.parameters.pattern) - ); - context.lineDashOffset = 0; //start point in array - } - context.save(); - context.beginPath(); - context.scale(1 / factor, 1); - context.arc(x1 * factor, y1, Math.abs(y - y1), 0, Math.PI, !isUpTrend); - if (this.pattern != "none") context.stroke(); - context.globalAlpha = 0.05; - context.fill(); - context.restore(); - if (context.setLineDash) context.setLineDash([]); - context.globalAlpha = 1; - } - context.textAlign = "left"; - // ensure we at least draw trend line from zero to 100 - var trendParameters = CIQ.clone(this.parameters.trend.parameters); - if (this.highlighted) trendParameters.opacity = 1; - this.stx.plotLine( - x1, - 2 * x0 - x1, - y1, - 2 * y0 - y1, - trendLineColor, - "segment", - context, - panel, - trendParameters - ); - if (this.highlighted) { - var p0Fill = this.highlighted == "p0" ? true : false; - var p1Fill = this.highlighted == "p1" ? true : false; - this.littleCircle(context, x0, y0, p0Fill); - this.littleCircle(context, x1, y1, p1Fill); - } - }; - - /** - * Fibonacci Fan drawing tool. - * - * It inherits its properties from {@link CIQ.Drawing.fibonacci} - * @constructor - * @name CIQ.Drawing.fibfan - * @since 2015-11-1 - * @version ChartIQ Advanced Package - */ - CIQ.Drawing.fibfan = function () { - this.name = "fibfan"; - //this.dragToDraw=true; - }; - - CIQ.inheritsFrom(CIQ.Drawing.fibfan, CIQ.Drawing.fibonacci); - - CIQ.Drawing.fibfan.prototype.recommendedLevels = [0, 0.382, 0.5, 0.618, 1]; - - CIQ.Drawing.fibfan.prototype.setOuter = function () {}; - - CIQ.Drawing.fibfan.prototype.render = function (context) { - var panel = this.stx.panels[this.panelName]; - if (!panel) return; - var yAxis = panel.yAxis; - if (!this.p1) return; - var x0 = this.stx.pixelFromTick(this.p0[0], panel.chart); - var x1 = this.stx.pixelFromTick(this.p1[0], panel.chart); - var y0 = this.stx.pixelFromValueAdjusted(panel, this.p0[0], this.p0[1]); - var y1 = this.stx.pixelFromValueAdjusted(panel, this.p1[0], this.p1[1]); - var top = Math.min(y1, y0); - var bottom = Math.max(y1, y0); - var height = bottom - top; - var isUpTrend = (y1 - y0) / (x1 - x0) > 0; - - var trendLineColor = this.getLineColor(this.parameters.trend.color); - - context.textBaseline = "middle"; - this.stx.canvasFont("stx_yaxis", context); // match font from y axis so it looks cohesive - var w = context.measureText("161.8%").width + 10; // give it extra space so it does not overlap with the price labels. - var /*minX=Number.MAX_VALUE,*/ minY = Number.MAX_VALUE, - /*maxX=Number.MAX_VALUE*-1,*/ maxY = Number.MAX_VALUE * -1; - var txtColor = this.color; - if (txtColor == "auto" || CIQ.isTransparent(txtColor)) - txtColor = this.stx.defaultColor; - this.rays = []; - for (var i = 0; i < this.parameters.fibs.length; i++) { - context.fillStyle = txtColor; - var fib = this.parameters.fibs[i]; - if (!fib.display) continue; - //var y=(y0-y1)*fib.level+y1; - var y = this.stx.pixelFromValueAdjusted( - panel, - this.p0[0], - (this.p0[1] - this.p1[1]) * fib.level + this.p1[1] - ); - var x = CIQ.xIntersection({ x0: x1, x1: x1, y0: y0, y1: y1 }, y); - var farX = panel.left; - if (x1 > x0) farX += panel.width; - var farY = ((farX - x0) * (y - y0)) / (x - x0) + y0; - if (x0 > farX - (this.parameters.printLevels ? w + 5 : 0) && x1 > x0) - continue; - else if (x0 < farX + (this.parameters.printLevels ? w + 5 : 0) && x1 < x0) - continue; - if (this.parameters.printLevels) { - var txt = Math.round(fib.level * 1000) / 10 + "%"; - if (x1 > x0) { - farX -= w; - context.textAlign = "left"; - } else { - farX += w; - context.textAlign = "right"; - } - if (this.parameters.printValues) { - context.fillStyle = txtColor; // the price labels screw up the color and font size...so reset before rendering the text - this.stx.canvasFont("stx_yaxis", context); // use the same context as the y axis so they match. - } - farY = ((farX - x0) * (y - y0)) / (x - x0) + y0; - context.fillText(txt, farX, farY); - if (x1 > x0) farX -= 5; - else farX += 5; - } - context.textAlign = "left"; - if (this.parameters.printValues) { - if (x < panel.width) { - // just use the actual price that segment will render on regardless of 'isUpTrend' since the values must match the prices on the y axis, and can not be reversed. - var price = this.stx.transformedPriceFromPixel(y, panel); - if (yAxis.priceFormatter) { - price = yAxis.priceFormatter(this.stx, panel, price); - } else { - price = this.stx.formatYAxisPrice(price, panel); - } - if (context == this.stx.chart.context) this.stx.endClip(); - this.stx.createYAxisLabel(panel, price, y, txtColor, null, context); - if (context == this.stx.chart.context) this.stx.startClip(panel.name); - } - } - var fibColor = fib.color; - if (fibColor == "auto" || CIQ.isTransparent(fibColor)) - fibColor = this.color; - if (fibColor == "auto" || CIQ.isTransparent(fibColor)) - fibColor = this.stx.defaultColor; - var fillColor = fib.color; - if (fillColor == "auto" || CIQ.isTransparent(fillColor)) - fillColor = this.fillColor; - if (fillColor == "auto" || CIQ.isTransparent(fillColor)) - fillColor = this.stx.defaultColor; - context.fillStyle = fillColor; - if (this.parameters.printLevels) - farY = ((farX - x0) * (y - y0)) / (x - x0) + y0; - var fibParameters = CIQ.clone(fib.parameters); - if (this.highlighted) fibParameters.opacity = 1; - this.stx.plotLine( - x0, - farX, - y0, - farY, - this.highlighted ? trendLineColor : fibColor, - "segment", - context, - panel, - fibParameters - ); - this.rays.push([ - [x0, y0], - [farX, farY] - ]); - context.globalAlpha = 0.05; - context.beginPath(); - context.moveTo(farX, farY); - context.lineTo(x0, y0); - context.lineTo(farX, y0); - context.fill(); - context.globalAlpha = 1; - if (y < minY) { - //minX=x; - minY = y; - } - if (y > maxY) { - //maxX=x; - maxY = y; - } - } - // ensure we at least draw trend line from zero to 100 - for (var level = 0; level <= 1; level++) { - var yy = isUpTrend ? bottom - height * level : top + height * level; - yy = Math.round(yy); - if (yy < minY) { - //minX=CIQ.xIntersection({x0:x1,x1:x1,y0:y0,y1:y1}, yy); - minY = yy; - } - if (yy > maxY) { - //maxX=CIQ.xIntersection({x0:x1,x1:x1,y0:y0,y1:y1}, yy); - maxY = yy; - } - } - //this.stx.plotLine(minX, maxX, minY, maxY, trendLineColor, "segment", context, panel, this.parameters.trend.parameters); - if (this.highlighted) { - var p0Fill = this.highlighted == "p0" ? true : false; - var p1Fill = this.highlighted == "p1" ? true : false; - this.littleCircle(context, x0, y0, p0Fill); - this.littleCircle(context, x1, y1, p1Fill); - } - }; - - /** - * Fibonacci Time Zone drawing tool. - * - * It inherits its properties from {@link CIQ.Drawing.fibonacci} - * @constructor - * @name CIQ.Drawing.fibtimezone - * @since 2015-11-1 - * @version ChartIQ Advanced Package - */ - CIQ.Drawing.fibtimezone = function () { - this.name = "fibtimezone"; - //this.dragToDraw=true; - }; - - CIQ.inheritsFrom(CIQ.Drawing.fibtimezone, CIQ.Drawing.fibonacci); - - CIQ.Drawing.fibtimezone.prototype.render = function (context) { - var panel = this.stx.panels[this.panelName]; - if (!panel) return; - if (!this.p1) return; - var x0 = this.stx.pixelFromTick(this.p0[0], panel.chart); - var x1 = this.stx.pixelFromTick(this.p1[0], panel.chart); - var y0 = this.stx.pixelFromValueAdjusted(panel, this.p0[0], this.p0[1]); - var y1 = this.stx.pixelFromValueAdjusted(panel, this.p1[0], this.p1[1]); - var fibs = [1, 0]; - - var trendLineColor = this.getLineColor(this.parameters.trend.color); - - context.textBaseline = "middle"; - this.stx.canvasFont("stx_yaxis", context); // match font from y axis so it looks cohesive - var h = 20; // give it extra space so it does not overlap with the date labels. - var mult = this.p1[0] - this.p0[0]; - var txtColor = this.color; - if (txtColor == "auto" || CIQ.isTransparent(txtColor)) - txtColor = this.stx.defaultColor; - context.textAlign = "center"; - - var x = x0; - var top = panel.yAxis.top; - var farY = panel.yAxis.bottom; - var txt = 0; - var fibColor = this.parameters.timezone.color; - if (fibColor == "auto" || CIQ.isTransparent(fibColor)) - fibColor = this.color; - if (fibColor == "auto" || CIQ.isTransparent(fibColor)) - fibColor = this.stx.defaultColor; - var fillColor = this.parameters.timezone.color; - if (fillColor == "auto" || CIQ.isTransparent(fillColor)) - fillColor = this.fillColor; - if (fillColor == "auto" || CIQ.isTransparent(fillColor)) - fillColor = this.stx.defaultColor; - - if (this.parameters.printLevels) farY -= h - 7; - - var tzParameters = CIQ.clone(this.parameters.timezone.parameters); - if (this.highlighted) tzParameters.opacity = 1; - do { - x = this.stx.pixelFromTick(this.p0[0] + txt * mult, panel.chart); - if (x0 < x1 && x > panel.left + panel.width) break; - else if (x0 > x1 && x < panel.left) break; - if (this.parameters.printLevels) { - context.fillStyle = txtColor; - context.fillText(x1 > x0 ? txt : txt * -1, x, farY + 7); - } - context.fillStyle = fillColor; - this.stx.plotLine( - x, - x, - 0, - farY, - this.highlighted ? trendLineColor : fibColor, - "segment", - context, - panel, - tzParameters - ); - context.globalAlpha = 0.05; - context.beginPath(); - context.moveTo(x0, top); - context.lineTo(x, top); - context.lineTo(x, farY); - context.lineTo(x0, farY); - context.fill(); - context.globalAlpha = 1; - txt = fibs[0] + fibs[1]; - fibs.unshift(txt); - } while (mult); - context.textAlign = "left"; - this.stx.plotLine( - x0, - x1, - y0, - y1, - trendLineColor, - "segment", - context, - panel, - tzParameters - ); - if (this.highlighted) { - var p0Fill = this.highlighted == "p0" ? true : false; - var p1Fill = this.highlighted == "p1" ? true : false; - this.littleCircle(context, x0, y0, p0Fill); - this.littleCircle(context, x1, y1, p1Fill); - } else { - // move points so always accessible - var yVal = this.stx.valueFromPixel(panel.height / 2, panel); - this.setPoint(0, this.p0[0], yVal, panel.chart); - this.setPoint(1, this.p1[0], yVal, panel.chart); - } - }; - - CIQ.Drawing.fibtimezone.prototype.intersected = function (tick, value, box) { - var p0 = this.p0, - p1 = this.p1, - panel = this.stx.panels[this.panelName]; - if (!p0 || !p1 || !panel) return null; // in case invalid drawing (such as from panel that no longer exists) - var pointsToCheck = { 0: p0, 1: p1 }; - for (var pt in pointsToCheck) { - if ( - this.pointIntersection(pointsToCheck[pt][0], pointsToCheck[pt][1], box) - ) { - this.highlighted = "p" + pt; - return { - action: "drag", - point: "p" + pt - }; - } - } - // Check for over the trend line or the 0 vertical line - var trendIntersects = this.lineIntersection(tick, value, box, "segment"); - if (trendIntersects || (box.x0 <= this.p0[0] && box.x1 >= p0[0])) { - this.highlighted = true; - return { - action: "move", - p0: CIQ.clone(p0), - p1: CIQ.clone(p1), - tick: tick, // save original tick - value: value // save original value - }; - } - return null; - }; - - // Backwards compatibility for drawings - CIQ.Drawing.arrow_v0 = function () { - this.name = "arrow"; - this.dimension = [11, 11]; - this.points = [ - [ - "M", - 3, - 0, - "L", - 7, - 0, - "L", - 7, - 5, - "L", - 10, - 5, - "L", - 5, - 10, - "L", - 0, - 5, - "L", - 3, - 5, - "L", - 3, - 0 - ] - ]; - }; - CIQ.inheritsFrom(CIQ.Drawing.arrow_v0, CIQ.Drawing.shape); - - /* Drawing specific shapes - * - * this.dimension: overall dimension of shape as designed, as a pair [dx,dy] where dx is length and dy is width, in pixels - * this.points: array of arrays. Each array represents a closed loop subshape. - * within each array is a series of values representing coordinates. - * For example, ["M",0,0,"L",1,1,"L",2,1,"Q",3,3,4,1,"B",5,5,0,0,3,3] - * The array will be parsed by the render function: - * "M" - move to the xy coordinates represented by the next 2 array elements - * "L" - draw line to xy coordinates represented by the next 2 array elements - * "Q" - draw quadratic curve where next 2 elements are the control point and following 2 elements are the end coordinates - * "B" - draw bezier curve where next 2 elements are first control point, next 2 elements are second control point, and next 2 elements are the end coordinates - * See sample shapes below. - * - */ - - CIQ.Drawing.xcross = function () { - this.name = "xcross"; - this.dimension = [7, 7]; - this.points=[ - [ - "M", 1, 0, - "L", 3, 2, - "L", 5, 0, - "L", 6, 1, - "L", 4, 3, - "L", 6, 5, - "L" ,5, 6, - "L", 3, 4, - "L", 1, 6, - "L", 0, 5, - "L", 2, 3, - "L", 0, 1, - "L", 1, 0 - ] - ]; // prettier-ignore - }; - CIQ.inheritsFrom(CIQ.Drawing.xcross, CIQ.Drawing.shape); - - CIQ.Drawing.check = function () { - this.name = "check"; - this.dimension = [8, 9]; - this.points = [ - [ - "M", 1, 5, - "L", 0, 6, - "L", 2, 8, - "L", 7, 1, - "L", 6, 0, - "L", 2, 6, - "L", 1, 5 - ] - ]; // prettier-ignore - }; - CIQ.inheritsFrom(CIQ.Drawing.check, CIQ.Drawing.shape); - - CIQ.Drawing.star = function () { - this.name = "star"; - this.dimension = [12, 12]; - this.points=[ - [ - "M", 0, 4, - "L", 4, 4, - "L", 5.5, 0, - "L", 7, 4, - "L", 11, 4, - "L" ,8, 7, - "L", 9, 11, - "L", 5.5, 9, - "L", 2, 11, - "L", 3, 7, - "L", 0, 4 - ] - ]; // prettier-ignore - }; - CIQ.inheritsFrom(CIQ.Drawing.star, CIQ.Drawing.shape); - - CIQ.Drawing.heart = function () { - this.name = "heart"; - this.dimension = [23, 20]; - this.points=[ - [ - "M", 11, 3, - "B", 11, 2.4, 10, 0, 6 ,0, - "B", 0, 0, 0, 7.5, 0, 7.5, - "B", 0, 11, 4, 15.4, 11, 19, - "B", 18, 15.4, 22, 11, 22, 7.5, - "B", 22, 7.5, 22, 0, 16, 0, - "B", 13, 0, 11, 2.4, 11, 3 - ] - ]; // prettier-ignore - }; - CIQ.inheritsFrom(CIQ.Drawing.heart, CIQ.Drawing.shape); - - CIQ.Drawing.focusarrow = function () { - this.name = "focusarrow"; - this.dimension = [7, 5]; - this.points = [ - [ - "M", 0, 0, - "L", 2, 2, - "L", 0, 4, - "L", 0, 0 - ], - [ - "M", 6, 0, - "L", 4, 2, - "L", 6, 4, - "L", 6, 0 - ] - ]; // prettier-ignore - }; - CIQ.inheritsFrom(CIQ.Drawing.focusarrow, CIQ.Drawing.shape); - - /** - * Crossline drawing tool. - * - * It inherits its properties from {@link CIQ.Drawing.horizontal} - * @constructor - * @name CIQ.Drawing.crossline - * @since 2016-09-19 - * @version ChartIQ Advanced Package - */ - CIQ.Drawing.crossline = function () { - this.name = "crossline"; - }; - CIQ.inheritsFrom(CIQ.Drawing.crossline, CIQ.Drawing.horizontal); - CIQ.extend( - CIQ.Drawing.crossline.prototype, - { - measure: function () {}, - accidentalClick: function (tick, value) { - return false; - }, - adjust: function () { - var panel = this.stx.panels[this.panelName]; - if (!panel) return; - this.setPoint(0, this.d0, this.v0, panel.chart); - this.p1 = CIQ.clone(this.p0); - }, - intersected: function (tick, value, box) { - if (!this.p0 || !this.p1) return null; - this.p1[0] += 1; - var isIntersected = this.lineIntersection(tick, value, box, "line"); - this.p1 = CIQ.clone(this.p0); - if (!isIntersected) { - this.p1[1] += 1; - isIntersected = this.lineIntersection(tick, value, box, "line"); - this.p1 = CIQ.clone(this.p0); - if (!isIntersected) return null; - } - this.highlighted = true; - if (this.pointIntersection(this.p0[0], this.p0[1], box)) { - this.highlighted = "p0"; - } - // This object will be used for repositioning - return { - action: "move", - p0: CIQ.clone(this.p0), - p1: CIQ.clone(this.p1), - tick: tick, // save original tick - value: value // save original value - }; - }, - render: function (context) { - var panel = this.stx.panels[this.panelName]; - if (!panel) return; - var x0 = this.stx.pixelFromTick(this.p0[0], panel.chart); - var y0 = this.stx.pixelFromValueAdjusted(panel, this.p0[0], this.p0[1]); - - var color = this.getLineColor(); - - var parameters = { - pattern: this.pattern, - lineWidth: this.lineWidth - }; - this.stx.plotLine( - x0, - x0 + 100, - y0, - y0, - color, - "horizontal", - context, - panel, - parameters - ); - this.stx.plotLine( - x0, - x0, - y0, - y0 + 100, - color, - "vertical", - context, - panel, - parameters - ); - - if (this.axisLabel && !this.repositioner) { - this.stx.endClip(); - var txt = this.p0[1]; - if (panel.chart.transformFunc) - txt = panel.chart.transformFunc(this.stx, panel.chart, txt); - if (panel.yAxis.priceFormatter) - txt = panel.yAxis.priceFormatter(this.stx, panel, txt); - else txt = this.stx.formatYAxisPrice(txt, panel); - this.stx.createYAxisLabel(panel, txt, y0, color); - this.stx.startClip(panel.name); - if (this.p0[0] >= 0 && !this.stx.chart.xAxis.noDraw) { - // don't try to compute dates from before dataSet - var dt, newDT; - /* set d0 to the right timezone */ - dt = this.stx.dateFromTick(this.p0[0], panel.chart, true); - if (!CIQ.ChartEngine.isDailyInterval(this.stx.layout.interval)) { - var milli = dt.getSeconds() * 1000 + dt.getMilliseconds(); - if (timezoneJS.Date && this.stx.displayZone) { - // this converts from the quote feed timezone to the chart specified time zone - newDT = new timezoneJS.Date(dt.getTime(), this.stx.displayZone); - dt = new Date( - newDT.getFullYear(), - newDT.getMonth(), - newDT.getDate(), - newDT.getHours(), - newDT.getMinutes() - ); - dt = new Date(dt.getTime() + milli); - } - } else { - dt.setHours(0, 0, 0, 0); - } - var myDate = CIQ.mmddhhmm(CIQ.yyyymmddhhmm(dt)); - /***********/ - if (panel.chart.xAxis.formatter) { - myDate = panel.chart.xAxis.formatter( - dt, - this.name, - null, - null, - myDate - ); - } else if (this.stx.internationalizer) { - var str; - if (dt.getHours() !== 0 || dt.getMinutes() !== 0) { - str = this.stx.internationalizer.monthDay.format(dt); - str += " " + this.stx.internationalizer.hourMinute.format(dt); - } else { - str = this.stx.internationalizer.yearMonthDay.format(dt); - } - myDate = str; - } - this.stx.endClip(); - this.stx.createXAxisLabel({ - panel: panel, - txt: myDate, - x: x0, - backgroundColor: color, - color: null, - pointed: true, - padding: 2 - }); - this.stx.startClip(panel.name); - } - } - if (this.highlighted) { - var p0Fill = this.highlighted == "p0" ? true : false; - this.littleCircle(context, x0, y0, p0Fill); - } - } - }, - true - ); - - /** - * Speed Resistance Arc drawing tool. - * - * It inherits its properties from {@link CIQ.Drawing.segment} - * @constructor - * @name CIQ.Drawing.speedarc - * @since 2016-09-19 - * @version ChartIQ Advanced Package - */ - CIQ.Drawing.speedarc = function () { - this.name = "speedarc"; - this.printLevels = true; - }; - CIQ.inheritsFrom(CIQ.Drawing.speedarc, CIQ.Drawing.segment); - CIQ.extend( - CIQ.Drawing.speedarc.prototype, - { - defaultOpacity: 0.25, - configs: ["color", "fillColor", "lineWidth", "pattern"], - copyConfig: function () { - this.color = this.stx.currentVectorParameters.currentColor; - this.fillColor = this.stx.currentVectorParameters.fillColor; - this.lineWidth = this.stx.currentVectorParameters.lineWidth; - this.pattern = this.stx.currentVectorParameters.pattern; - }, - intersected: function (tick, value, box) { - if (!this.p0 || !this.p1) return null; // in case invalid drawing (such as from panel that no longer exists) - var pointsToCheck = { 0: this.p0, 1: this.p1 }; - for (var pt in pointsToCheck) { - if ( - this.pointIntersection( - pointsToCheck[pt][0], - pointsToCheck[pt][1], - box - ) - ) { - this.highlighted = "p" + pt; - return { - action: "drag", - point: "p" + pt - }; - } - } - var isIntersected = this.lineIntersection(tick, value, box, this.name); - if (isIntersected) { - this.highlighted = true; - // This object will be used for repositioning - return { - action: "move", - p0: CIQ.clone(this.p0), - p1: CIQ.clone(this.p1), - tick: tick, // save original tick - value: value // save original value - }; - } - - // Just test the box circumscribing the arcs - var left = this.p1[0] - (this.p0[0] - this.p1[0]); - var right = this.p0[0]; - var bottom = this.p1[1]; - var top = this.p0[1]; - - if (tick > Math.max(left, right) || tick < Math.min(left, right)) - return null; - if (value > Math.max(top, bottom) || value < Math.min(top, bottom)) - return null; - this.highlighted = true; - return { - action: "move", - p0: CIQ.clone(this.p0), - p1: CIQ.clone(this.p1), - tick: tick, - value: value - }; - }, - render: function (context) { - var panel = this.stx.panels[this.panelName]; - if (!panel) return; - if (!this.p1) return; - var x0 = this.stx.pixelFromTick(this.p0[0], panel.chart); - var x1 = this.stx.pixelFromTick(this.p1[0], panel.chart); - var y0 = this.stx.pixelFromValueAdjusted(panel, this.p0[0], this.p0[1]); - var y1 = this.stx.pixelFromValueAdjusted(panel, this.p1[0], this.p1[1]); - var isUpTrend = y1 < y0; - var factor = Math.abs((y1 - y0) / (x1 - x0)); - - var color = this.getLineColor(); - context.strokeStyle = color; - var fillColor = this.fillColor; - if (fillColor == "auto" || CIQ.isTransparent(fillColor)) - fillColor = this.stx.defaultColor; - context.fillStyle = fillColor; - if (context.setLineDash) { - context.setLineDash( - CIQ.borderPatternToArray(this.lineWidth, this.pattern) - ); - context.lineDashOffset = 0; //start point in array - } - this.stx.canvasFont("stx_yaxis", context); - for (var i = 1; i < 3; i++) { - var radius = - (Math.abs(this.p1[1] - this.p0[1]) * Math.sqrt(2) * i) / 3; - var value = - this.p1[1] + - radius * (isUpTrend ? -1 : 1) * (panel.yAxis.flipped ? -1 : 1); - var y = this.stx.pixelFromValueAdjusted(panel, this.p0[0], value); - - context.save(); - context.beginPath(); - context.scale(1 / factor, 1); - context.arc( - x1 * factor, - y1, - Math.abs(y - y1), - 0, - Math.PI, - !isUpTrend - ); - context.globalAlpha = this.highlighted ? 1 : this.defaultOpacity; - if (this.pattern != "none") context.stroke(); - context.globalAlpha = 0.1; - context.fill(); - context.restore(); - context.globalAlpha = 1; - if (this.printLevels) { - context.fillStyle = color; - context.textAlign = "center"; - var txt = i + "/3"; - context.fillText(txt, x1, Math.round(y - 5)); - context.fillStyle = fillColor; - } - } - context.textAlign = "left"; - var parameters = { - pattern: this.pattern, - lineWidth: this.lineWidth, - opacity: this.highlighted ? 1 : this.defaultOpacity - }; - this.stx.plotLine( - x0, - x1, - y0, - y1, - color, - "segment", - context, - panel, - parameters - ); - if (context.setLineDash) context.setLineDash([]); - if (this.highlighted) { - var p0Fill = this.highlighted == "p0" ? true : false; - var p1Fill = this.highlighted == "p1" ? true : false; - this.littleCircle(context, x0, y0, p0Fill); - this.littleCircle(context, x1, y1, p1Fill); - } - }, - reconstruct: function (stx, obj) { - this.stx = stx; - this.color = obj.col; - this.fillColor = obj.fc; - this.panelName = obj.pnl; - this.pattern = obj.ptrn; - this.lineWidth = obj.lw; - this.d0 = obj.d0; - this.d1 = obj.d1; - this.tzo0 = obj.tzo0; - this.tzo1 = obj.tzo1; - this.v0 = obj.v0; - this.v1 = obj.v1; - this.adjust(); - }, - serialize: function () { - return { - name: this.name, - pnl: this.panelName, - col: this.color, - fc: this.fillColor, - ptrn: this.pattern, - lw: this.lineWidth, - d0: this.d0, - d1: this.d1, - tzo0: this.tzo0, - tzo1: this.tzo1, - v0: this.v0, - v1: this.v1 - }; - } - }, - true - ); - - /** - * Speed Resistance Lines drawing tool. - * - * It inherits its properties from {@link CIQ.Drawing.speedarc} - * @constructor - * @name CIQ.Drawing.speedline - * @since 2016-09-19 - * @version ChartIQ Advanced Package - */ - CIQ.Drawing.speedline = function () { - this.name = "speedline"; - this.printLevels = true; - }; - CIQ.inheritsFrom(CIQ.Drawing.speedline, CIQ.Drawing.speedarc); - CIQ.extend( - CIQ.Drawing.speedline.prototype, - { - intersected: function (tick, value, box) { - var p0 = this.p0, - p1 = this.p1; - if (!p0 || !p1) return null; // in case invalid drawing (such as from panel that no longer exists) - var pointsToCheck = { 0: p0, 1: p1 }; - for (var pt in pointsToCheck) { - if ( - this.pointIntersection( - pointsToCheck[pt][0], - pointsToCheck[pt][1], - box - ) - ) { - this.highlighted = "p" + pt; - return { - action: "drag", - point: "p" + pt - }; - } - } - var rays = this.rays; - for (var i = 0; i < rays.length; i++) { - if ( - this.lineIntersection( - tick, - value, - box, - "ray", - rays[i][0], - rays[i][1], - true - ) - ) { - this.highlighted = true; - // This object will be used for repositioning - return { - action: "move", - p0: CIQ.clone(p0), - p1: CIQ.clone(p1), - tick: tick, // save original tick - value: value // save original value - }; - } - } - return null; - }, - render: function (context) { - var panel = this.stx.panels[this.panelName]; - if (!panel) return; - if (!this.p1) return; - var x0 = this.stx.pixelFromTick(this.p0[0], panel.chart); - var x1 = this.stx.pixelFromTick(this.p1[0], panel.chart); - var y0 = this.stx.pixelFromValueAdjusted(panel, this.p0[0], this.p0[1]); - var y1 = this.stx.pixelFromValueAdjusted(panel, this.p1[0], this.p1[1]); - this.stx.canvasFont("stx_yaxis", context); // match font from y axis so it looks cohesive - var trendLineColor = this.getLineColor(); - var color = this.color; - if (color == "auto" || CIQ.isTransparent(color)) - color = this.stx.defaultColor; - context.strokeStyle = color; - var fillColor = this.fillColor; - if (fillColor == "auto" || CIQ.isTransparent(fillColor)) - fillColor = this.stx.defaultColor; - context.fillStyle = fillColor; - var parameters = { - pattern: this.pattern, - lineWidth: this.lineWidth, - opacity: this.highlighted ? 1 : this.defaultOpacity - }; - var farX0, farY0; - var levels = ["1", "2/3", "1/3", "3/2", "3"]; - var levelValues = [1, 2 / 3, 1 / 3, 3 / 2, 3]; - var grids = []; - this.rays = []; - for (var i = 0; i < levelValues.length; i++) { - var level = levelValues[i]; - if (level > 1 && !this.extension) continue; - var y = this.stx.pixelFromValueAdjusted( - panel, - this.p0[0], - this.p0[1] - (this.p0[1] - this.p1[1]) * level - ); - var x; - if (level > 1) { - x = CIQ.xIntersection({ x0: x0, x1: x1, y0: y0, y1: y }, y1); - grids.push(x); - } else { - x = CIQ.xIntersection({ x0: x1, x1: x1, y0: y0, y1: y1 }, y); - grids.push(y); - } - //var x=x0+(x1-x0)/level; - //var y=y0-level*(y0-y1); - var farX = level > 1 ? x : x1; - var farY = level > 1 ? y1 : y; - if (!this.confineToGrid) { - farX = panel.left; - if (x1 > x0) farX += panel.width; - farY = ((farX - x0) * (y - y0)) / (x1 - x0) + y0; - } - if (this.printLevels) { - if (level != 1 || this.extension) { - context.fillStyle = color; - var perturbX = 0, - perturbY = 0; - if (y0 > y1) { - perturbY = -5; - context.textBaseline = "bottom"; - } else { - perturbY = 5; - context.textBaseline = "top"; - } - if (x0 > x1) { - perturbX = 5; - context.textAlign = "right"; - } else { - perturbX = -5; - context.textAlign = "left"; - } - if (level > 1) - context.fillText( - levels[i], - x + (this.confineToGrid ? 0 : perturbX), - y1 - ); - else - context.fillText( - levels[i], - x1, - y + (this.confineToGrid ? 0 : perturbY) - ); - context.fillStyle = fillColor; - } - } - this.stx.plotLine( - x0, - farX, - y0, - farY, - this.highlighted ? trendLineColor : color, - "segment", - context, - panel, - parameters - ); - if (level == 1) { - farX0 = farX; - farY0 = farY; - } - this.rays.push([ - [x0, y0], - [farX, farY] - ]); - context.globalAlpha = 0.1; - context.beginPath(); - context.moveTo(farX, farY); - context.lineTo(x0, y0); - context.lineTo(farX0, farY0); - context.fill(); - context.globalAlpha = 1; - } - context.textAlign = "left"; - context.textBaseline = "middle"; - if (this.confineToGrid) { - context.globalAlpha = 0.3; - context.beginPath(); - context.strokeRect(x0, y0, x1 - x0, y1 - y0); - context.moveTo(x0, grids[1]); - context.lineTo(x1, grids[1]); - context.moveTo(x0, grids[2]); - context.lineTo(x1, grids[2]); - if (this.extension) { - context.moveTo(grids[3], y0); - context.lineTo(grids[3], y1); - context.moveTo(grids[4], y0); - context.lineTo(grids[4], y1); - } - context.stroke(); - context.globalAlpha = 1; - } - if (this.highlighted) { - var p0Fill = this.highlighted == "p0" ? true : false; - var p1Fill = this.highlighted == "p1" ? true : false; - this.littleCircle(context, x0, y0, p0Fill); - this.littleCircle(context, x1, y1, p1Fill); - } - } - }, - true - ); - - /** - * Gann Fan drawing tool. - * - * It inherits its properties from {@link CIQ.Drawing.speedarc} - * @constructor - * @name CIQ.Drawing.gannfan - * @since 2016-09-19 - * @version ChartIQ Advanced Package - */ - CIQ.Drawing.gannfan = function () { - this.name = "gannfan"; - this.printLevels = true; - }; - CIQ.inheritsFrom(CIQ.Drawing.gannfan, CIQ.Drawing.speedline); - CIQ.extend( - CIQ.Drawing.gannfan.prototype, - { - render: function (context) { - var panel = this.stx.panels[this.panelName]; - if (!panel) return; - if (!this.p1) return; - var x0 = this.stx.pixelFromTick(this.p0[0], panel.chart); - var x1 = this.stx.pixelFromTick(this.p1[0], panel.chart); - var y0 = this.stx.pixelFromValueAdjusted(panel, this.p0[0], this.p0[1]); - var y1 = this.stx.pixelFromValueAdjusted(panel, this.p1[0], this.p1[1]); - this.stx.canvasFont("stx_yaxis", context); // match font from y axis so it looks cohesive - var trendLineColor = this.getLineColor(); - var color = this.color; - if (color == "auto" || CIQ.isTransparent(color)) - color = this.stx.defaultColor; - context.strokeStyle = color; - var fillColor = this.fillColor; - if (fillColor == "auto" || CIQ.isTransparent(fillColor)) - fillColor = this.stx.defaultColor; - context.fillStyle = fillColor; - var parameters = { - pattern: this.pattern, - lineWidth: this.lineWidth, - opacity: this.highlighted ? 1 : this.defaultOpacity - }; - var farX0, farY0; - var levels = [1, 2, 3, 4, 8, 1 / 2, 1 / 3, 1 / 4, 1 / 8]; - this.rays = []; - for (var i = 0; i < levels.length; i++) { - var level = levels[i]; - var x = x0 + (x1 - x0) / level; - var y = y0 - level * (y0 - y1); - var farX = panel.left; - if (x1 > x0) farX += panel.width; - var farY = ((farX - x0) * (y - y0)) / (x1 - x0) + y0; - if (this.printLevels) { - context.fillStyle = color; - var perturbX = 0, - perturbY = 0; - if (y0 > y1) { - perturbY = 5; - context.textBaseline = "top"; - } else { - perturbY = -5; - context.textBaseline = "bottom"; - } - if (x0 > x1) { - perturbX = 5; - context.textAlign = "left"; - } else { - perturbX = -5; - context.textAlign = "right"; - } - if (level > 1) { - context.fillText(level + "x1", x + perturbX, y1); - } else { - context.fillText("1x" + 1 / level, x1, y + perturbY); - } - context.fillStyle = fillColor; - } - this.stx.plotLine( - x0, - farX, - y0, - farY, - this.highlighted ? trendLineColor : color, - "segment", - context, - panel, - parameters - ); - this.rays.push([ - [x0, y0], - [farX, farY] - ]); - if (level == 1) { - farX0 = farX; - farY0 = farY; - } - context.globalAlpha = 0.1; - context.beginPath(); - context.moveTo(farX, farY); - context.lineTo(x0, y0); - context.lineTo(farX0, farY0); - context.fill(); - context.globalAlpha = 1; - } - context.textAlign = "left"; - context.textBaseline = "middle"; - if (this.highlighted) { - var p0Fill = this.highlighted == "p0" ? true : false; - var p1Fill = this.highlighted == "p1" ? true : false; - this.littleCircle(context, x0, y0, p0Fill); - this.littleCircle(context, x1, y1, p1Fill); - } - } - }, - true - ); - - /** - * Time Cycle drawing tool. - * - * It inherits its properties from {@link CIQ.Drawing.speedarc} - * @constructor - * @name CIQ.Drawing.timecycle - * @since 2016-09-19 - * @version ChartIQ Advanced Package - */ - CIQ.Drawing.timecycle = function () { - this.name = "timecycle"; - this.printLevels = true; - }; - CIQ.inheritsFrom(CIQ.Drawing.timecycle, CIQ.Drawing.speedarc); - CIQ.extend( - CIQ.Drawing.timecycle.prototype, - { - intersected: function (tick, value, box) { - var p0 = this.p0, - p1 = this.p1, - panel = this.stx.panels[this.panelName]; - if (!p0 || !p1 || !panel) return null; // in case invalid drawing (such as from panel that no longer exists) - var pointsToCheck = { 0: p0, 1: p1 }; - for (var pt in pointsToCheck) { - if ( - this.pointIntersection( - pointsToCheck[pt][0], - pointsToCheck[pt][1], - box - ) - ) { - this.highlighted = "p" + pt; - return { - action: "drag", - point: "p" + pt - }; - } - } - // Check for over the trend line or the 0 vertical line - var trendIntersects = this.lineIntersection( - tick, - value, - box, - "segment" - ); - if (trendIntersects || (box.x0 <= this.p0[0] && box.x1 >= p0[0])) { - this.highlighted = true; - return { - action: "move", - p0: CIQ.clone(p0), - p1: CIQ.clone(p1), - tick: tick, // save original tick - value: value // save original value - }; - } - return null; - }, - render: function (context) { - var panel = this.stx.panels[this.panelName]; - if (!panel) return; - if (!this.p1) return; - - var x0 = this.stx.pixelFromTick(this.p0[0], panel.chart); - var x1 = this.stx.pixelFromTick(this.p1[0], panel.chart); - var y0 = this.stx.pixelFromValueAdjusted(panel, this.p0[0], this.p0[1]); - var y1 = this.stx.pixelFromValueAdjusted(panel, this.p1[0], this.p1[1]); - var count = 0; - - var trendLineColor = this.getLineColor(); - context.textBaseline = "middle"; - this.stx.canvasFont("stx_yaxis", context); // match font from y axis so it looks cohesive - var h = 20; // give it extra space so it does not overlap with the date labels. - var mult = this.p1[0] - this.p0[0]; - context.textAlign = "center"; - - var x = x0; - var top = panel.yAxis.top; - var farY = panel.yAxis.bottom; - var color = this.color; - if (color == "auto" || CIQ.isTransparent(color)) - color = this.stx.defaultColor; - var fillColor = this.fillColor; - if (fillColor == "auto" || CIQ.isTransparent(fillColor)) - fillColor = this.stx.defaultColor; - - if (this.printLevels) farY -= h - 7; - - var parameters = { - pattern: this.pattern, - lineWidth: this.lineWidth, - opacity: this.highlighted ? 1 : this.defaultOpacity - }; - - var x_s = []; - context.save(); - context.fillStyle = fillColor; - context.globalAlpha = 0.05; - //context.globalCompositeOperation="destination-over"; - do { - x = this.stx.pixelFromTick(this.p0[0] + count * mult, panel.chart); - count++; - - if (x0 < x1 && x > panel.left + panel.width) break; - else if (x0 > x1 && x < panel.left) break; - else if (x < panel.left || x > panel.left + panel.width) continue; - - context.beginPath(); - context.moveTo(x0, top); - context.lineTo(x, top); - context.lineTo(x, farY); - context.lineTo(x0, farY); - context.fill(); - x_s.push({ c: count, x: x }); - } while (mult); - context.globalAlpha = 1; - var slack = 0; - for (var pt = 0; pt < x_s.length; pt++) { - this.stx.plotLine( - x_s[pt].x, - x_s[pt].x, - 0, - farY, - this.highlighted ? trendLineColor : color, - "segment", - context, - panel, - parameters - ); - if (this.printLevels) { - context.fillStyle = color; - var m = this.stx.chart.context.measureText(x_s[pt].c).width + 3; - if (m < this.stx.layout.candleWidth + slack) { - context.fillText(x_s[pt].c, x_s[pt].x, farY + 7); - slack = 0; - } else { - slack += this.stx.layout.candleWidth; - } - } - } - context.restore(); - context.textAlign = "left"; - - this.stx.plotLine( - x0, - x1, - y0, - y1, - trendLineColor, - "segment", - context, - panel, - parameters - ); - if (this.highlighted) { - var p0Fill = this.highlighted == "p0" ? true : false; - var p1Fill = this.highlighted == "p1" ? true : false; - this.littleCircle(context, x0, y0, p0Fill); - this.littleCircle(context, x1, y1, p1Fill); - } else { - // move points so always accessible - var yVal = this.stx.valueFromPixel(panel.height / 2, panel); - this.setPoint(0, this.p0[0], yVal, panel.chart); - this.setPoint(1, this.p1[0], yVal, panel.chart); - } - } - }, - true - ); - - /** - * Regression Line drawing tool. - * - * It inherits its properties from {@link CIQ.Drawing.segment} - * @constructor - * @name CIQ.Drawing.regression - * @since 2016-09-19 - * @version ChartIQ Advanced Package - */ - CIQ.Drawing.regression = function () { - this.name = "regression"; - }; - CIQ.inheritsFrom(CIQ.Drawing.regression, CIQ.Drawing.segment); - CIQ.extend( - CIQ.Drawing.regression.prototype, - { - configs: [ - // primary line - "color", - "lineWidth", - "pattern", - // stddev * 1 - "active1", - "color1", - "lineWidth1", - "pattern1", - // stddev * 2 - "active2", - "color2", - "lineWidth2", - "pattern2", - // stddev * 3 - "active3", - "color3", - "lineWidth3", - "pattern3" - ], - copyConfig: function (withPreferences) { - CIQ.Drawing.copyConfig(this, withPreferences); - var cvp = this.stx.currentVectorParameters; - this.active1 = !!cvp.active1; - this.active2 = !!cvp.active2; - this.active3 = !!cvp.active3; - this.color1 = cvp.color1 || "auto"; - this.color2 = cvp.color2 || "auto"; - this.color3 = cvp.color3 || "auto"; - this.lineWidth1 = cvp.lineWidth1; - this.lineWidth2 = cvp.lineWidth2; - this.lineWidth3 = cvp.lineWidth3; - this.pattern1 = cvp.pattern1; - this.pattern2 = cvp.pattern2; - this.pattern3 = cvp.pattern3; - }, - $controls: [ - 'cq-cvp-controller[cq-cvp-header="1"]', - 'cq-cvp-controller[cq-cvp-header="2"]', - 'cq-cvp-controller[cq-cvp-header="3"]' - ], - click: function (context, tick, value) { - if (tick < 0) return; - this.copyConfig(); - var panel = this.stx.panels[this.panelName]; - if (!this.penDown) { - this.setPoint(0, tick, value, panel.chart); - this.penDown = true; - var stx = this.stx; - this.field = stx.highlightedDataSetField; - if (!this.field && panel != stx.chart.panel) { - for (var sr in stx.chart.seriesRenderers) { - var renderer = stx.chart.seriesRenderers[sr]; - if (renderer.params.panel == panel.name) { - this.field = renderer.seriesParams[0].field; - break; - } - } - for (var st in stx.layout.studies) { - var study = stx.layout.studies[st]; // find a default study on this panel - if (study.panel == panel.name) { - this.field = Object.keys(study.outputMap)[0]; - break; - } - } - } - return false; - } - if (this.accidentalClick(tick, value)) return this.dragToDraw; - - this.setPoint(1, tick, value, panel.chart); - this.penDown = false; - return true; // kernel will call render after this - }, - // Returns both the transformed and untransformed value of the drawing's field attribute - getYValue: function (i) { - var record = this.stx.chart.dataSet[i], - transformedRecord = this.stx.chart.dataSet[i]; - if (!record) return null; - - var panel = this.stx.panels[this.panelName]; - var yAxis = this.stx.getYAxisByField(panel, this.field) || panel.yAxis; - if ( - this.stx.charts[panel.name] && - panel.chart.transformFunc && - yAxis == panel.yAxis - ) - transformedRecord = record.transform; - if (!transformedRecord) return null; - - var price = null, - transformedPrice = null, - defaultField = this.stx.defaultPlotField || "Close"; - if (this.field) { - transformedPrice = CIQ.existsInObjectChain( - transformedRecord, - this.field - ); - if (!transformedPrice) return null; - price = transformedPrice = - transformedPrice.obj[transformedPrice.member]; - if (record != transformedRecord) { - price = CIQ.existsInObjectChain(record, this.field); - price = price.obj[price.member]; - } - if (typeof transformedPrice == "object") { - transformedPrice = transformedPrice[defaultField]; - price = price[defaultField]; - } - } else { - transformedPrice = transformedRecord[defaultField]; - price = record[defaultField]; - } - return { transformed: transformedPrice, untransformed: price }; - }, - render: function (context) { - var panel = this.stx.panels[this.panelName]; - if (!panel) return; - if (!this.p1) return; - if (this.p0[0] < 0 || this.p1[0] < 0) return; - var x0 = this.stx.pixelFromTick(this.p0[0], panel.chart); - var x1 = this.stx.pixelFromTick(this.p1[0], panel.chart); - if (x0 < panel.left && x1 < panel.left) return; - if (x0 > panel.right && x1 > panel.right) return; - var yAxis = this.stx.getYAxisByField(panel, this.field); - - var prices = [], - rawPrices = []; // rawPrices used solely for measure - var sumCloses = 0, - sumRawCloses = 0; - var sumWeightedCloses = 0, - sumWeightedRawCloses = 0; - var start = Math.min(this.p1[0], this.p0[0]); - var end = Math.max(this.p1[0], this.p0[0]) + 1; - var rawTicks = end - start; - for (var i = start; i < end; i++) { - var price = this.getYValue(i); - if (price) { - prices.push(price.transformed); - rawPrices.push(price.untransformed); - } - } - - var ticks = prices.length; - var sumWeights = (ticks * (ticks + 1)) / 2; - var squaredSumWeights = Math.pow(sumWeights, 2); - var sumWeightsSquared = (sumWeights * (2 * ticks + 1)) / 3; - - for (i = 0; i < ticks; i++) { - sumWeightedCloses += ticks * prices[i] - sumCloses; - sumCloses += prices[i]; - sumWeightedRawCloses += ticks * rawPrices[i] - sumRawCloses; - sumRawCloses += rawPrices[i]; - } - - var slope = - (ticks * sumWeightedCloses - sumWeights * sumCloses) / - (ticks * sumWeightsSquared - squaredSumWeights); - var intercept = (sumCloses - slope * sumWeights) / ticks; - var rawSlope = - (ticks * sumWeightedRawCloses - sumWeights * sumRawCloses) / - (ticks * sumWeightsSquared - squaredSumWeights); - var rawIntercept = (sumRawCloses - slope * sumWeights) / ticks; - var v0, v1; - if (this.p0[0] < this.p1[0]) { - v0 = intercept; - v1 = slope * rawTicks + intercept; - this.p0[1] = rawIntercept; - this.p1[1] = rawSlope * rawTicks + rawIntercept; - } else { - v0 = slope * rawTicks + intercept; - v1 = intercept; - this.p0[1] = rawSlope * rawTicks + rawIntercept; - this.p1[1] = rawIntercept; - } - - var y0 = this.stx.pixelFromTransformedValue(v0, panel, yAxis); - var y1 = this.stx.pixelFromTransformedValue(v1, panel, yAxis); - var trendLineColor = this.getLineColor(); - var parameters = { - pattern: this.pattern, - lineWidth: this.lineWidth - }; - this.stx.plotLine( - x0, - x1, - y0, - y1, - trendLineColor, - "segment", - context, - panel, - parameters - ); - this.stx.plotLine( - x0, - x0, - y0 - 20, - y0 + 20, - trendLineColor, - "segment", - context, - panel, - parameters - ); - this.stx.plotLine( - x1, - x1, - y1 - 20, - y1 + 20, - trendLineColor, - "segment", - context, - panel, - parameters - ); - - if (this.active1 || this.active2 || this.active3) { - var average = sumCloses / ticks; - var sumStddev = 0; - - for (i = 0; i < ticks; i++) { - sumStddev += Math.pow(prices[i] - average, 2); - } - - var stddev = Math.sqrt(sumStddev / ticks); - var params = { - context: context, - panel: panel, - points: { - 0: { x: x0, v: v0 }, - 1: { x: x1, v: v1 } - }, - stddev: stddev, - yAxis: yAxis - }; - - this.lines = {}; - - if (this.active1) { - this.renderStddev("1", "p", params); - this.renderStddev("1", "n", params); - } - - if (this.active2) { - this.renderStddev("2", "p", params); - this.renderStddev("2", "n", params); - } - - if (this.active3) { - this.renderStddev("3", "p", params); - this.renderStddev("3", "n", params); - } - } - - if (!this.highlighted) { - this.pixelX = [x0, x1]; - this.pixelY = [y0, y1]; - } else { - var p0Fill = this.highlighted == "p0" ? true : false; - var p1Fill = this.highlighted == "p1" ? true : false; - this.littleCircle(context, x0, y0, p0Fill); - this.littleCircle(context, x1, y1, p1Fill); - } - }, - renderStddev: function (scope, sign, parameters) { - var name = "stddev" + scope + sign; - var colorKey = "color" + scope; - var patternKey = "pattern" + scope; - var lineWidthKey = "lineWidth" + scope; - var points = parameters.points; - var v0 = points[0].v; - var v1 = points[1].v; - var stddev = parameters.stddev; - var stddevMult = sign === "n" ? scope * -1 : scope * 1; - var stx = this.stx; - var panel = parameters.panel; - var yAxis = parameters.yAxis; - var line = { - name: name, - color: this.getLineColor(this[colorKey]), - type: "segment", - y0: stx.pixelFromTransformedValue( - v0 + stddev * stddevMult, - panel, - yAxis - ), - y1: stx.pixelFromTransformedValue( - v1 + stddev * stddevMult, - panel, - yAxis - ), - params: { - pattern: this[patternKey], - lineWidth: this[lineWidthKey] - } - }; - - // set line for intersected method - if (this.lines) { - this.lines[name] = line; - } - - var context = parameters.context; - var x0 = points[0].x; - var x1 = points[1].x; - - stx.plotLine( - x0, - x1, - line.y0, - line.y1, - line.color, - line.type, - context, - panel, - line.params - ); - stx.plotLine( - x0, - x0, - line.y0 - 10, - line.y0 + 10, - line.color, - line.type, - context, - panel, - line.params - ); - stx.plotLine( - x1, - x1, - line.y1 - 10, - line.y1 + 10, - line.color, - line.type, - context, - panel, - line.params - ); - - var label = scope + "\u03c3"; - var labelX = Math.max(x0, x1) + 5; - var labelY = x0 < x1 ? line.y1 : line.y0; - - context.fillStyle = line.color; - context.save(); - context.textBaseline = "middle"; - context.fillText(label, labelX, labelY); - context.restore(); - - // derived class `average` has an axisLabel - if ( - parameters.formatPrice && - this.axisLabel && - !this.highlighted && - !this.penDown - ) { - if ( - (x0 >= panel.chart.left && x0 <= panel.chart.right) || - (x1 >= panel.chart.left && x1 <= panel.chart.right) - ) { - var displayPrice = (x0 < x1 ? v1 : v0) + stddev * stddevMult; - stx.endClip(); - stx.createYAxisLabel( - panel, - parameters.formatPrice(displayPrice, yAxis), - labelY, - line.color, - null, - context, - yAxis - ); - stx.startClip(panel.name); - } - } - }, - intersected: function (tick, value, box) { - if (!this.pixelX || !this.pixelY) return null; - - var repositionIntersection = this.repositionIntersection(tick, value); - if (repositionIntersection) return repositionIntersection; - - // check for point intersection - var pointsToCheck = { 0: this.pixelX, 1: this.pixelY }; - for (var pt = 0; pt < 2; pt++) { - if ( - this.pointIntersection( - pointsToCheck[0][pt], - pointsToCheck[1][pt], - box, - true - ) - ) { - this.highlighted = "p" + pt; - return { - action: "drag", - point: "p" + pt - }; - } - } - - // check for line intersection - var self = this; - var x0 = this.pixelX[0]; - var x1 = this.pixelX[1]; - var lineIntersection = function (line) { - var p0 = [x0, line.y0]; - var p1 = [x1, line.y1]; - - return self.lineIntersection( - tick, - value, - box, - self.name, - p0, - p1, - true - ); - }; - var isIntersected = lineIntersection({ - y0: this.pixelY[0], - y1: this.pixelY[1] - }); - - if (!isIntersected && this.lines) { - for (var key in this.lines) { - if (lineIntersection(this.lines[key])) { - isIntersected = true; - break; - } - } - } - - if (isIntersected) { - this.highlighted = true; - // This object will be used for repositioning - return { - action: "move", - p0: CIQ.clone(this.p0), - p1: CIQ.clone(this.p1), - tick: tick, // save original tick - value: value // save original value - }; - } - - return null; - }, - repositionIntersection: function (tick, value) { - if (!this.p0 || !this.p1) return false; // in case invalid drawing (such as from panel that no longer exists) - if (this == this.stx.repositioningDrawing && this.highlighted) { - // already moving or dragging, continue - if (this.highlighted === true) { - return { - action: "move", - p0: CIQ.clone(this.p0), - p1: CIQ.clone(this.p1), - tick: tick, // save original tick - value: value // save original value - }; - } - return { - action: "drag", - point: this.highlighted - }; - } - return false; - }, - lineIntersection: function (tick, value, box, type, p0, p1, isPixels) { - if (!isPixels) { - console.log( - type + - " lineIntersection must accept p0 and p1 in pixels. Please verify and set isPixels=true." - ); - return false; - } - if (!p0) p0 = this.p0; - if (!p1) p1 = this.p1; - if (!(p0 && p1)) return false; - var stx = this.stx; - var pixelBox = CIQ.convertBoxToPixels(stx, this.panelName, box); - if (pixelBox.x0 === undefined) return false; - var pixelPoint = { x0: p0[0], x1: p1[0], y0: p0[1], y1: p1[1] }; - return CIQ.boxIntersects( - pixelBox.x0, - pixelBox.y0, - pixelBox.x1, - pixelBox.y1, - pixelPoint.x0, - pixelPoint.y0, - pixelPoint.x1, - pixelPoint.y1 - ); - }, - boxIntersection: function (tick, value, box) { - if ( - box.cx0 > Math.max(this.pixelX[0], this.pixelX[1]) || - box.cx1 < Math.min(this.pixelX[0], this.pixelX[1]) - ) - return false; - if ( - !this.stx.repositioningDrawing && - (box.cy1 < this.pixelY[0] || box.cy0 > this.pixelY[1]) - ) - return false; - return true; - }, - reconstruct: function (stx, obj) { - this.stx = stx; - this.color = obj.col; - this.color1 = obj.col1; - this.color2 = obj.col2; - this.color3 = obj.col3; - this.active1 = obj.dev1; - this.active2 = obj.dev2; - this.active3 = obj.dev3; - this.panelName = obj.pnl; - this.pattern = obj.ptrn; - this.pattern1 = obj.ptrn1; - this.pattern2 = obj.ptrn2; - this.pattern3 = obj.ptrn3; - this.lineWidth = obj.lw; - this.lineWidth1 = obj.lw1; - this.lineWidth2 = obj.lw2; - this.lineWidth3 = obj.lw3; - this.d0 = obj.d0; - this.d1 = obj.d1; - this.tzo0 = obj.tzo0; - this.tzo1 = obj.tzo1; - this.field = obj.fld; - this.adjust(); - }, - serialize: function () { - return { - name: this.name, - pnl: this.panelName, - dev1: this.active1, - dev2: this.active2, - dev3: this.active3, - col: this.color, - col1: this.color1, - col2: this.color2, - col3: this.color3, - ptrn: this.pattern, - ptrn1: this.pattern1, - ptrn2: this.pattern2, - ptrn3: this.pattern3, - lw: this.lineWidth, - lw1: this.lineWidth1, - lw2: this.lineWidth2, - lw3: this.lineWidth3, - d0: this.d0, - d1: this.d1, - tzo0: this.tzo0, - tzo1: this.tzo1, - fld: this.field - }; - } - }, - true - ); - - /** - * trendline is an implementation of a {@link CIQ.Drawing.segment} drawing. - * - * Extends {@link CIQ.Drawing.segment} and automatically renders a {@link CIQ.Drawing.callout} - * containing trend information. - * @constructor - * @name CIQ.Drawing.trendline - * @since 5.1.2 - * @version ChartIQ Advanced Package - */ - CIQ.Drawing.trendline = function () { - this.name = "trendline"; - }; - - CIQ.inheritsFrom(CIQ.Drawing.trendline, CIQ.Drawing.segment); - - // allow configuration of font for trendline info in callout, which is then assigned later - CIQ.Drawing.trendline.prototype.configs = [ - "color", - "fillColor", - "lineWidth", - "pattern", - "font" - ]; - - CIQ.Drawing.trendline.prototype.measure = function () { - // empty function since the text will now display in a callout - }; - - CIQ.Drawing.trendline.prototype.reconstruct = function (stx, obj) { - // reconstruct segment as usual, then add callout as property - CIQ.Drawing.segment.prototype.reconstruct.call(this, stx, obj); - this.callout = new CIQ.Drawing.callout(); - this.callout.reconstruct(stx, obj.callout); - }; - - CIQ.Drawing.trendline.prototype.serialize = function () { - // serialize segment as usual, then add callout as property - var obj = CIQ.Drawing.segment.prototype.serialize.call(this); - obj.callout = this.callout.serialize(); - return obj; - }; - - CIQ.Drawing.trendline.prototype.render = function (context) { - var panel = this.stx.panels[this.panelName]; - if (!panel) return; - - // render segment as usual - CIQ.Drawing.segment.prototype.render.call(this, context); - - // only create and initialize callout once - if (!this.callout) { - this.callout = new CIQ.Drawing.callout(); - var obj = CIQ.Drawing.segment.prototype.serialize.call(this); - this.callout.reconstruct(this.stx, obj); - } - - // always render the callout perpendicular above / below the segment / trendline - this.callout.p0 = CIQ.clone(this.p0); - - // extract segment coordinates - var x0 = this.stx.pixelFromTick(this.p0[0], panel.chart); - var x1 = this.stx.pixelFromTick(this.p1[0], panel.chart); - var y0 = this.stx.pixelFromValueAdjusted(panel, this.p0[0], this.p0[1]); - var y1 = this.stx.pixelFromValueAdjusted(panel, this.p1[0], this.p1[1]); - - // return if we are off the screen axes else insanity ensues - if (!isFinite(y0) || !isFinite(y1)) return; - - // calculate midpoint (for stem of callout) - var xmid = (x0 + x1) / 2; - var ymid = (y0 + y1) / 2; - - // determine length of segment and multiplier / direction of normal vector to give fixed length depending on stem location - this.fontSize = CIQ.stripPX((this.font && this.font.size) || 13); - var stemDist = - this.callout.w * 1.2 + (this.callout.stemEntry[0] == "c" ? 0 : 50); - var segmentDist = Math.sqrt(Math.pow(x1 - x0, 2) + Math.pow(y1 - y0, 2)); - var scalar = - (stemDist / (segmentDist || stemDist)) * - (this.p1[1] < this.p0[1] ? 1 : -1); - - // normal vector (see e.g. http://mathworld.wolfram.com/NormalVector.html) - var nX = -(y1 - ymid) * scalar + xmid; - var nY = (x1 - xmid) * scalar + ymid; - - // assign callout coordinates - this.callout.p0[0] = this.stx.tickFromPixel(nX, panel.chart); - this.callout.p0[1] = this.stx.priceFromPixel(nY, panel); - this.callout.v0 = this.callout.p0[1]; - this.callout.p1 = CIQ.clone(this.p0); - - // assign callout properties - this.callout.stx = this.stx; - this.callout.fillColor = this.fillColor || this.callout.fillColor; - this.callout.borderColor = this.color; - this.callout.font = this.font || this.callout.font; - this.callout.noHandles = true; - - // calculate trend and assign to callout text; only show percent if not Inf - var deltaV = this.p1[1] - this.p0[1]; - this.callout.text = - "" + - Number(deltaV).toFixed(2) + - (this.p0[1] === 0 - ? "" - : " (" + Number((100 * deltaV) / this.p0[1]).toFixed(2) + "%) ") + - "" + - Math.abs(this.p1[0] - this.p0[0]) + - " Bars"; - - // calculate stem as midpoint of segment - var midtickIdx = Math.floor((this.p0[0] + this.p1[0]) / 2), - midV; - if ( - Math.abs(this.p0[0] - this.p1[0]) > 1 && - Math.abs(this.p0[0] - this.p1[0]) < 20 - ) { - // because of math.floor, we may be grabbing a bar off of center, - // so calculate price based on slope of trendline - var midtickXpixel = this.stx.pixelFromTick(midtickIdx, panel.chart); - var midtickYpixel = y0 + ((y1 - y0) / (x1 - x0)) * (midtickXpixel - x0); - midV = this.stx.priceFromPixel(midtickYpixel, panel) || ymid; - } else { - midV = this.stx.priceFromPixel(ymid, panel); - } - - this.callout.stem = { - t: midtickIdx, - v: midV - }; - - // render callout and text - this.callout.renderText(); - this.callout.render(context); - - // paint the handle circles based on highlighting - if (this.highlighted) { - var p0Fill = this.highlighted == "p0" ? true : false; - var p1Fill = this.highlighted == "p1" ? true : false; - this.littleCircle(context, x0, y0, p0Fill); - this.littleCircle(context, x1, y1, p1Fill); - } - }; - - CIQ.Drawing.trendline.prototype.lineIntersection = function ( - tick, - value, - box, - type - ) { - // override type as segment to preserve lineIntersection functionality - return CIQ.Drawing.BaseTwoPoint.prototype.lineIntersection.call( - this, - tick, - value, - box, - "segment" - ); - }; - - CIQ.Drawing.trendline.prototype.intersected = function (tick, value, box) { - // in case invalid drawing (such as from panel that no longer exists) - if (!this.p0 || !this.p1) return null; - - // call and store intersection methods on both callout and segment - var calloutIntersected = this.callout.intersected(tick, value, box); - var segmentIntersected = CIQ.Drawing.segment.prototype.intersected.call( - this, - tick, - value, - box - ); - - // synchronize highlighting - this.callout.highlighted = !!(calloutIntersected || segmentIntersected); - //this.highlighted = segmentIntersected || calloutIntersected; - - if (segmentIntersected) { - // If segment is highlighted, return as usual; - return segmentIntersected; - } else if (calloutIntersected) { - // Otherwise, if callout is highlighted, move segment (callout will follow / rerender) - return { - action: "move", - p0: CIQ.clone(this.p0), - p1: CIQ.clone(this.p1), - tick: tick, // save original tick - value: value // save original value - }; - } - - // neither are intersected - return null; - }; - - /** - * Average Line drawing tool. - * - * It inherits its properties from {@link CIQ.Drawing.regression} - * @constructor - * @name CIQ.Drawing.average - * @since 4.0.0 - * @version ChartIQ Advanced Package - */ - CIQ.Drawing.average = function () { - this.name = "average"; - }; - CIQ.inheritsFrom(CIQ.Drawing.average, CIQ.Drawing.regression); - CIQ.extend( - CIQ.Drawing.average.prototype, - { - configs: CIQ.Drawing.regression.prototype.configs.concat("axisLabel"), - measure: function () { - if (this.p0 && this.p1) { - this.stx.setMeasure( - 0, - false, - this.p0[0], - this.p1[0], - true, - this.name - ); - var txt = [], - html = ""; - if (this.active1) txt.push("1"); - if (this.active2) txt.push("2"); - if (this.active3) txt.push("3"); - if (txt.length) html = " " + txt.join(", ") + " σ"; - var mMeasure = (this.stx.drawingContainer || document).querySelector( - ".mMeasure" - ); - var mSticky = this.stx.controls.mSticky; - var mStickyInterior = - mSticky && mSticky.querySelector(".mStickyInterior"); - if (mMeasure) mMeasure.innerHTML += html; - if (mStickyInterior) { - var lines = []; - lines.push(CIQ.capitalize(this.name)); - lines.push(this.field || this.stx.defaultPlotField || "Close"); - lines.push(mStickyInterior.innerHTML + html); - mStickyInterior.innerHTML = lines.join("stxx.createDataSet(); stxx.draw();
to trigger this function.
- *
- * Expected Format :
- *
- * fc(stxChart, dataSet);
- *
- * @type {function}
- * @alias transformDataSetPre
- * @memberof CIQ.ChartEngine
- * @instance
- * @example
- * stxx.transformDataSetPre=function(stxx, dataSet){
- * for(var i=0;i < dataSet.length;i++){
- * // do something to the dataset here
- * }
- * }
- */
- this.transformDataSetPre = null;
- /**
- * Register this function to transform the data set after a createDataSet() event; such as change in periodicity.
- * You can also explicitly call stxx.createDataSet(); stxx.draw();
to trigger this function.
- *
- * Expected Format :
- *
- * fc(stxChart, dataSet, min low price in the dataset, max high price in the dataset);
- *
- * @type {function}
- * @alias transformDataSetPost
- * @memberof CIQ.ChartEngine
- * @instance
- * @example
- * stxx.transformDataSetPost=function(self, dataSet, min, max){
- * for(var i=0;i < dataSet.length;i++){
- * // do something to the dataset here
- * }
- * }
- */
- this.transformDataSetPost = null;
- /**
- * This is the callback function used by {@link CIQ.ChartEngine#setPeriodicity} when no quotefeed has been attached to the chart.
- * Called if the masterData does not have the interval requested.
- *
- * Do not initialize if you are using a {@link quotefeed }
- *
- * @type {function}
- * @alias dataCallback
- * @memberof CIQ.ChartEngine
- * @instance
- * @example
- * stxx.dataCallback=function(){
- * // put code here to get the new data in the correct periodicity.
- * // use layout.interval and layout.periodicity to determine what you need.
- * // finally call stxx.loadChart(symbol,data) to load the data and render the chart.
- * }
- */
- this.dataCallback = null;
- /**
- * Stores a list of active drawing object on the chart. Serialized renditions of drawings can be added using {@link CIQ.ChartEngine#createDrawing} and removed using {@link CIQ.ChartEngine#removeDrawing}
- * @type array
- * @default
- * @alias drawingObjects
- * @memberof CIQ.ChartEngine
- * @instance
- */
- this.drawingObjects = [];
- this.undoStamps = [];
- /**
- * READ ONLY. Flag that specifies whether the background canvas should be used to draw grid lines and axes.
- * This flag is set to true when the `canvasShim` contains child elements. The `canvasShim` is the background
- * canvas — an HTML container behind the main chart canvas.
- *
- * Check this flag to determine whether the `canvasShim` is being used to create background drawings.
- *
- * @see {@link CIQ.Visualization}
- * @see {@link CIQ.ChartEngine#embedVisualization}.
- * @type boolean
- * @default
- * @alias useBackgroundCanvas
- * @memberof CIQ.ChartEngine
- * @instance
- * @since 7.4.0
- */
- this.useBackgroundCanvas = false;
- /**
- * READ ONLY. Access the renderer controlling the main series.
- * @type CIQ.Renderer
- * @default
- * @alias mainSeriesRenderer
- * @memberof CIQ.ChartEngine
- * @instance
- */
- this.mainSeriesRenderer = null;
- /**
- * Object that stores the styles used by the chart.
- * @type object
- * @alias styles
- * @instance
- * @memberof CIQ.ChartEngine
- */
- this.styles = {}; // Contains CSS styles used internally to render canvas elements
- /**
- * Placeholder for plugin data sets. This array will register each plug in object, complete with their functions.
- *
- * If defined, Plug-in instances will be called by their corresponding native functions for the following:
- * - consolidate ( called by {@link CIQ.ChartEngine#consolidatedQuote})
- * - drawUnder (called by draw before rendering underlays)
- * - drawOver (called by draw after rendering overlays)
- * - {@link CIQ.ChartEngine#setMasterData}
- * - {@link CIQ.ChartEngine#updateChartData}
- * - {@link CIQ.ChartEngine#initializeChart}
- * - {@link CIQ.ChartEngine#createDataSet}
- * - {@link CIQ.ChartEngine#findHighlights}
- * @type array
- * @memberof CIQ.ChartEngine
- * @instance
- * @private
- */
- this.plugins = {};
- /**
- * Cloned copy of {@link CIQ.ChartEngine.currentVectorParameters} object template.
- * Use it to store the settings for the active drawing tool.
- * @type {CIQ.ChartEngine.currentVectorParameters}
- * @default
- * @alias currentVectorParameters
- * @memberof CIQ.ChartEngine
- * @instance
- * @tsdeclaration
- * public currentVectorParameters: typeof CIQ.ChartEngine.currentVectorParameters
- */
- this.currentVectorParameters = CIQ.clone(
- CIQ.ChartEngine.currentVectorParameters
- ); // contains the current drawing parameters for this chart
- /**
- * Holds {@link CIQ.ChartEngine.Chart} object
- * @type CIQ.ChartEngine.Chart
- * @default
- * @alias chart
- * @memberof CIQ.ChartEngine
- * @instance
- */
- this.chart = new CIQ.ChartEngine.Chart();
- var chart = this.chart;
- chart.name = "chart";
- chart.yAxis.name = "chart";
- chart.canvas = null; // Contains the HTML5 canvas with the chart and drawings
- chart.tempCanvas = null; // lays on top of the canvas and is used when creating drawings
- chart.container = config.container;
- if (CIQ.Market) chart.market = new CIQ.Market(); //create a default market, always open
- this.charts.chart = chart;
-
- CIQ.extend(this, config);
-
- if (config.container) {
- if (this.registerHTMLElements) this.registerHTMLElements();
- // Initialize the very basic dimensions of chart so that it is operational immediately
- chart.width = chart.container.clientWidth - chart.yAxis.width;
- this.setCandleWidth(this.layout.candleWidth, chart);
- chart.canvasHeight = chart.container.clientHeight;
- }
- this.construct();
- };
-
- /**
- * READ ONLY. Toggles to true when a drawing is initiated
- * @type boolean
- * @default
- * @alias drawingLine
- * @static
- * @memberof CIQ.ChartEngine
- */
- CIQ.ChartEngine.drawingLine = false;
- /**
- * READ ONLY. Toggles to true when a panel is being resized
- * @type boolean
- * @default
- * @alias resizingPanel
- * @static
- * @memberof CIQ.ChartEngine
- */
- CIQ.ChartEngine.resizingPanel = null;
- /**
- * READ ONLY. Current X screen coordinate of the crosshair.
- * @type number
- * @default
- * @alias crosshairX
- * @static
- * @memberof CIQ.ChartEngine
- */
- CIQ.ChartEngine.crosshairX = 0;
- /**
- * READ ONLY. Current Y screen coordinate of the crosshair.
- * @type number
- * @default
- * @alias crosshairY
- * @static
- * @memberof CIQ.ChartEngine
- */
- CIQ.ChartEngine.crosshairY = 0;
- /**
- * [Browser animation API](https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame) is on by default.
- * @type boolean
- * @default
- * @alias useAnimation
- * @static
- * @memberof CIQ.ChartEngine
- */
- CIQ.ChartEngine.useAnimation = true;
-
- CIQ.ChartEngine.enableCaching = false;
- /**
- * Set to true to true to bypass all touch event handling.
- * @type number
- * @default
- * @alias ignoreTouch
- * @static
- * @memberof CIQ.ChartEngine
- */
- CIQ.ChartEngine.ignoreTouch = false;
- /**
- * Mitigates problems clearing the canvas on old (defective) Android devices by performing additional function on the canvas, normally not needed on the newer devices.
- * Set to false to boost native android browser performance, but at risk of "double candle" display errors on some older devices.
- * @type boolean
- * @default
- * @alias useOldAndroidClear
- * @static
- * @memberof CIQ.ChartEngine
- */
- CIQ.ChartEngine.useOldAndroidClear = true;
-
- // Documented in standard/drawing.js
- CIQ.ChartEngine.currentVectorParameters = {};
-
- /**
- * If set to a valid time zone identifier, then new CIQ.ChartEngine objects will pull their display timezone from this.
- * @type {string}
- * @alias defaultDisplayTimeZone
- * @static
- * @memberof CIQ.ChartEngine
- */
- CIQ.ChartEngine.defaultDisplayTimeZone = null; // If set, then new CIQ.ChartEngine objects will pull their display timezone from this
-
- /**
- * If set, overrides the default base path for plug-ins.
- *
- * By default, plug-ins loaded by means of a script tag check for resources inside the
- * plug-ins directory, `plugins/`. However, if the application is served from outside the
- * `chartiq` directory, or the plug-ins folder is otherwise not available at `./`, you may
- * need to specify where the plug-ins directory can be found so resources can be loaded.
- *
- * Path must end in `/`.
- *
- * @type string
- * @default
- * @alias pluginBasePath
- * @static
- * @memberof CIQ.ChartEngine
- * @since 8.0.0
- */
- CIQ.ChartEngine.pluginBasePath = "plugins/";
-
- CIQ.ChartEngine.registeredContainers = []; // This will contain an array of all of the CIQ container objects
- // Note that if you are dynamically destroying containers in the DOM you should delete them from this array when you do so
-
- /**
- * Calls the functions in {@link CIQ.ChartEngine.helpersToRegister} to instantiate the registered
- * chart helpers.
- *
- * @param {CIQ.ChartEngine} stx A chart engine reference, which is passed to the functions in
- * {@link CIQ.ChartEngine.helpersToRegister}.
- *
- * @memberof CIQ.ChartEngine
- * @private
- * @since 8.2.0
- */
- CIQ.ChartEngine.registerHelpers = function (stx) {
- CIQ.ChartEngine.helpersToRegister.forEach(function (registrationFn) {
- registrationFn(stx);
- });
- };
-
- /**
- * An array of functions that instantiate helpers for the chart engine.
- *
- * Modules that define a chart helper should push a function to this array so that the helper can
- * be created by {@link CIQ.ChartEngine.registerHelpers}. The function should include a parameter
- * of type {@link CIQ.ChartEngine} and attach the helper to the chart engine referenced by the
- * parameter (see example).
- *
- * @type function[]
- * @memberof CIQ.ChartEngine
- * @private
- * @since 8.2.0
- *
- * @example
- * CIQ.ChartEngine.helpersToRegister.push(function (stx) {
- * stx.baselineHelper = new Map();
- * });
- */
- CIQ.ChartEngine.helpersToRegister = [];
-
- /**
- * Private construction of the chart object. This is called from the actual constructor
- * for CIQ.ChartEngine.
- * @private
- * @memberof CIQ.ChartEngine
- * @since
- * - 07/01/2015
- * - 7.1.0 Changed `longHoldTime` to 700ms default.
- */
- CIQ.ChartEngine.prototype.construct = function () {
- if (this.createChartPanel) {
- this.stackPanel("chart", "chart", 1);
- this.adjustPanelPositions();
- this.chart.panel = this.panels[this.chart.name];
- }
- this.cx = 0;
- this.cy = 0;
- this.micropixels = 0;
- this.callbackListeners = {
- /**
- * Called by {@link CIQ.ChartEngine.AdvancedInjectable#touchDoubleClick} when the chart
- * is quickly tapped twice.
- *
- * @param {object} data Data relevant to the "tap" event.
- * @param {CIQ.ChartEngine} data.stx The chart engine instance.
- * @param {number} data.finger Indicates which finger double-tapped.
- * @param {number} data.x The crosshairs x-coordinate.
- * @param {number} data.y The crosshairs y-coordinate.
- *
- * @callback CIQ.ChartEngine~doubleTapEventListener
- * @since 4.0.0
- *
- * @see {@link CIQ.ChartEngine#addEventListener}
- */
- doubleTap: [],
- /**
- * Called by {@link CIQ.ChartEngine#doubleClick} when the chart is quickly clicked or
- * tapped twice.
- *
- * @param {object} data Data relevant to the double-click or double-tap event.
- * @param {CIQ.ChartEngine} data.stx The chart engine instance.
- * @param {number} data.button The button or finger that double-clicked or
- * double-tapped.
- * @param {number} data.x The double-click or crosshairs x-coordinate.
- * @param {number} data.y The double-click or crosshairs y-coordinate.
- *
- * @callback CIQ.ChartEngine~doubleClickEventListener
- * @since 8.0.0
- *
- * @see {@link CIQ.ChartEngine#addEventListener}
- */
- doubleClick: [],
- /**
- * Called when a drawing is added, removed, or modified.
- *
- * Such as calling {@link CIQ.ChartEngine#clearDrawings},
- * {@link CIQ.ChartEngine#removeDrawing}, {@link CIQ.ChartEngine#undoLast}, or
- * {@link CIQ.ChartEngine#drawingClick}.
- *
- * @param {object} data Data relevant to the "drawing" event.
- * @param {CIQ.ChartEngine} data.stx The chart engine instance.
- * @param {string} data.symbol The current chart symbol.
- * @param {object} data.symbolObject The symbol's value and display label
- * ({@link CIQ.ChartEngine.Chart#symbolObject}).
- * @param {object} data.layout The chart's layout object ({@link CIQ.ChartEngine#layout}).
- * @param {Array} data.drawings The chart's current drawings ({@link CIQ.Drawing}).
- *
- * @callback CIQ.ChartEngine~drawingEventListener
- *
- * @see {@link CIQ.ChartEngine#addEventListener}
- */
- drawing: [],
- /**
- * A right-click on a highlighted drawing.
- *
- * @param {object} data Data relevant to the "drawingEdit" event.
- * @param {CIQ.ChartEngine} data.stx The chart engine instance.
- * @param {CIQ.Drawing} data.drawing The highlighted drawing instance.
- *
- * @callback CIQ.ChartEngine~drawingEditEventListener
- *
- * @see {@link CIQ.ChartEngine#addEventListener}
- */
- drawingEdit: [],
- /**
- * Called to open a window that can be moved and resized by the user.
- *
- * For example, called by {@link CIQ.Shortcuts} to display the keyboard shortcuts legend.
- *
- * @param {object} data Data relevant to the "floatingWindow" event.
- * @param {string} data.type The type of floating window to open; for example, "shortcut"
- * for a floating window containing the keyboard shortcuts legend (see
- * {@link CIQ.Shortcuts}).
- * @param {string} data.content The contents of the floating window, typically an HTML
- * string.
- * @param {object} [data.container] The DOM element that visually contains the floating
- * window. The window is positioned on screen relative to the element (see
- * {@link WebComponents.cq-floating-window.DocWindow#positionRelativeTo}). Defaults
- * to `document.body`.
- * **Note:** The markup of the DOM element does not need to lexically contain the - * markup of the floating window. - * @param {string} [data.title] Text that appears in the title bar of the floating window. - * @param {number} [data.width] The width of the floating window in pixels. - * @param {boolean} [data.status] The floating window state: true, to open the floating - * window; false, to close it. If the parameter is not provided, the floating window - * is toggled (opened if closed, closed if open). - * @param {string} [data.tag] A label that identifies the floating window type; for - * example, "shortcut", which indicates that the floating window contains the keyboard - * shortcuts legend. - *
**Note:** Use this parameter to manage floating windows in a multi-chart
- * document. Only one instance of a floating window is created for a given tag
- * regardless of how many "floatingWindow" events occur having that tag, in which
- * case a floating window can be shared by multiple charts. If floating windows do
- * not have tags, new floating windows are created for new "floatingWindow" events
- * even though the events may have the same `type` (see above).
- * @param {function} [data.onClose] A callback to execute when the floating window is
- * closed.
- *
- * @callback CIQ.ChartEngine~floatingWindowEventListener
- * @since 8.2.0
- *
- * @see {@link CIQ.ChartEngine#addEventListener}
- */
- floatingWindow: [],
- /**
- * Called when a change occurs in the chart layout.
- *
- * Layout changes are caused by:
- * - Calling {@link CIQ.ChartEngine#setChartType},
- * {@link CIQ.ChartEngine#setAggregationType}, {@link CIQ.ChartEngine#setChartScale}, or
- * {@link CIQ.ChartEngine#setAdjusted}
- * - Using the {@link WebComponents.cq-toolbar} to disable the current active drawing tool
- * or toggling the crosshair
- * - Using the {@link WebComponents.cq-views} to activate a serialized layout
- * - Modifying a series ({@link CIQ.ChartEngine#modifySeries})
- * - Setting a new periodicity ({@link CIQ.ChartEngine#setPeriodicity})
- * - Adding or removing a study overlay
- * ({@link CIQ.ChartEngine.AdvancedInjectable#removeOverlay})
- * - Adding or removing any new panels (and their corresponding studies)
- * - Zooming in ({@link CIQ.ChartEngine#zoomIn}) or
- * zooming out ({@link CIQ.ChartEngine#zoomOut})
- * - Setting ranges with {@link CIQ.ChartEngine#setSpan} or
- * {@link CIQ.ChartEngine#setRange}
- * - Nullifying a programmatically set span or range by user panning
- * - Enabling or disabling [extended hours]{@link CIQ.ExtendedHours}
- * - Toggling the [range slider]{@link CIQ.RangeSlider}
- *
- * **Note** Scrolling and panning changes are not considered a layout change but rather a
- * shift of the view window in the same layout. To detect those, register to listen for
- * ["scroll" events]{@link CIQ.ChartEngine~scrollEventListener}.
- *
- * @param {object} data Data relevant to the "layout" event.
- * @param {CIQ.ChartEngine} data.stx The chart engine instance.
- * @param {string} data.symbol The current chart symbol.
- * @param {object} data.symbolObject The symbol's value and display label
- * ({@link CIQ.ChartEngine.Chart#symbolObject}).
- * @param {object} data.layout The chart's layout object ({@link CIQ.ChartEngine#layout}).
- * @param {Array} data.drawings The chart's current drawings ({@link CIQ.Drawing}).
- *
- * @callback CIQ.ChartEngine~layoutEventListener
- *
- * @see {@link CIQ.ChartEngine#addEventListener}
- */
- layout: [],
- /**
- * Called when the mouse is clicked on the chart and held down.
- *
- * @param {object} data Data relevant to the "longhold" event.
- * @param {CIQ.ChartEngine} data.stx The chart engine instance.
- * @param {string} data.panel The panel being clicked.
- * @param {number} data.x The crosshair x-coordinate.
- * @param {number} data.y The crosshair y-coordinate.
- *
- * @callback CIQ.ChartEngine~longholdEventListener
- *
- * @see {@link CIQ.ChartEngine#addEventListener}
- */
- longhold: [],
- /**
- * Called when the pointer is moved inside the chart, even on panning or horizontal
- * swiping.
- *
- * @param {object} data Data relevant to the "move" event.
- * @param {CIQ.ChartEngine} data.stx The chart engine instance.
- * @param {string} data.panel The panel where the mouse is active.
- * @param {number} data.x The pointer x-coordinate.
- * @param {number} data.y The pointer y-coordinate.
- * @param {boolean} data.grab True if the chart is being dragged.
- *
- * @callback CIQ.ChartEngine~moveEventListener
- *
- * @see {@link CIQ.ChartEngine#addEventListener}
- */
- move: [],
- /**
- * Called when the [quotefeed interface](quotefeed.html) loads a complete data set as
- * a result of:
- * - [symbol changes]{@link CIQ.ChartEngine#loadChart} or
- * - [periodicity]{@link CIQ.ChartEngine#setPeriodicity},
- * [range]{@link CIQ.ChartEngine#setRange}, or [span]{@link CIQ.ChartEngine#setSpan}
- * changes requiring new data.
- *
- * @param {object} data Data relevant to the "newChart" event.
- * @param {CIQ.ChartEngine} data.stx The chart engine instance.
- * @param {string} data.symbol The current chart symbol.
- * @param {object} data.symbolObject The symbol's value and display label,
- * {@link CIQ.ChartEngine.Chart#symbolObject}.
- * @param {boolean} data.moreAvailable True if {@link quotefeed~dataCallback} reports
- * that more data is available.
- * @param {boolean} data.upToDate True if {@link quotefeed~dataCallback} reports that
- * no more future data is available.
- * @param {object} data.quoteDriver The quote feed driver.
- *
- * @callback CIQ.ChartEngine~newChartEventListener
- * @since 8.0.0 Added the `upToDate` parameter.
- *
- * @see {@link CIQ.ChartEngine#addEventListener}
- */
- newChart: [],
- /**
- * Called when a message toaster notification event (a toast) has occurred.
- *
- * @param {object} data Data relevant to the notification event.
- * @param {string} data.message Text to display in the notification.
- * @param {string} [data.position="top"] Alignment of the notification: "top" or "bottom".
- * Overrides the `defaultPosition` attribute of the
- * [` Argument format can be: **Note:** If this parameter is a string, the optional `cb` parameter is required.
- * @param {string} obj.type The type of event.
- * @param {function} obj.cb The listener to be removed.
- * @param {function} [cb] The listener to be removed. Required if the `obj` parameter is an
- * string, unused otherwise.
- *
- * @memberof CIQ.ChartEngine
- * @since 04-2016-08
- */
- CIQ.ChartEngine.prototype.removeEventListener = function (obj, cb) {
- if (!obj || typeof obj != "object") {
- // User likely passed in type and callback as two separate arguments into this function.
- // This is accounted for because it is consistent with the argument schema of "addEventListener"
- obj = {
- type: obj,
- cb: cb
- };
- }
-
- var spliceEvent = function (arr, cb) {
- for (var i = 0; i < arr.length; i++) {
- if (arr[i] === cb) {
- arr.splice(i, 1);
- return;
- }
- }
- };
- var callbackListeners = this.callbackListeners;
-
- if (obj.type === "*") {
- for (var key in callbackListeners) {
- spliceEvent(callbackListeners[key], obj.cb);
- }
- return;
- }
-
- if (!callbackListeners[obj.type]) {
- throw new Error("Attempted to remove an invalid listener.");
- }
-
- spliceEvent(callbackListeners[obj.type], obj.cb);
- };
-
- /**
- * Dispatches an event by calling one or more
- * [event listeners]{@link CIQ.ChartEngine#eventListeners} registered for the event specified by
- * `type`. Event listeners registered for the `*` event type are also subsequently called.
- * See {@link CIQ.ChartEngine#addEventListener}.
- *
- * **Note:** If any of the called event listeners returns true, all remaining uncalled
- * listeners are bypassed.
- *
- * @param {string} type Identifies the type of event for which the event listeners are called.
- * Must be one of the types listed in {@link CIQ.ChartEngine#addEventListener} excluding `*`.
- * @param {object} data A collection of parameters to provide to the listener functions called in
- * response to the event. See the listener types listed in
- * {@link CIQ.ChartEngine#addEventListener} for relevant parameters.
- * @return {boolean} False unless a called listener returns true, in which case this function
- * also returns true.
- *
- * @memberof CIQ.ChartEngine
- *
- * @example
- * // Trigger a layout change event; perhaps to save the layout.
- * stx.dispatch("layout", {
- * stx: stx,
- * symbol: stx.chart.symbol,
- * symbolObject: stx.chart.symbolObject,
- * layout: stx.layout,
- * drawings: stx.drawingObjects
- * });
- */
- CIQ.ChartEngine.prototype.dispatch = function (type, data) {
- var arr = this.callbackListeners[type];
- if (arr) {
- for (var i = 0; i < arr.length; i++) {
- if (arr[i].call(this, data) === true) return true;
- }
- }
- arr = this.callbackListeners["*"];
- if (arr) {
- for (var j = 0; j < arr.length; j++) {
- if (arr[j].call(this, data) === true) return true;
- }
- }
- return false;
- };
-
- //@private
- CIQ.ChartEngine.prototype.updateListeners = function (event) {
- for (var i in this.plugins) {
- var plugin = this.plugins[i];
- if (plugin.display && plugin.listener) plugin.listener(this, event);
- }
- };
-
- };
-
-
- let __js_core_engine_injection_ = (_exports) => {
-
-
- var CIQ = _exports.CIQ;
-
- /**
- * The following is a list of ADVANCED injectable methods.
- *
- * **These methods should not be normally called by your code, but rather injections should be used to modify their behavior within the library Kernel.**
- *
- * The "Injection API" provides prepend and append functionality to any built-in method.
- * Essentially what this means is that a developer can write code that will be run either before (prepend) or after (append) any internal {@link CIQ.ChartEngine} function (such as draw() or mouseMove()).
- * This gives developers the ability to supplement, override or ignore any of the built in functionality.
- *
- * Note that you may prepend or append multiple functions. Each injected function is stacked "outward" (daisy-chained) from the core function.
- *
- * _prepend >> prepend >> prepend >> function << append << append << append_
- *
- * You may prepend/append either to CIQ.ChartEngine.prototype or directly to a CIQ.ChartEngine instance.
- *
- * See the {@tutorial Using the Injection API} and [Customization Basics](tutorial-Customization%20Basics.html#injections) tutorials for additional guidance and examples.
- * @namespace CIQ.ChartEngine.AdvancedInjectable
- * @example
- * CIQ.ChartEngine.prototype.append("method_name_goes_here", function(){
- * // do something here
- * });
- * @example
- * CIQ.ChartEngine.prototype.prepend("method_name_goes_here", function(){
- * // do something here
- * // return true; // if you want to exit the method after your injection
- * // return false; // if you want the standard code to follow the prepend
- * });
- */
-
- /**
- * Prepends custom developer functionality to an internal chart member. See [“Injection API"]{@tutorial Using the Injection API}.
- * @param {string} o Signature of member
- * @param {function} n Callback function, will be called with "apply"
- * @memberof CIQ.ChartEngine
- * @since
- * - 04-2015 You can append either to an {@link CIQ.ChartEngine} instance, or to the prototype. The first will affect only a single
- * chart while the latter will affect any chart (if you have multiple on the screen).
- * - 15-07-01 Function returns a descriptor which can be passed in to [removeInjection()]{@link CIQ.ChartEngine#removeInjection} to remove it later on.
- * @return {object} Injection descriptor which can be passed in to {@link CIQ.ChartEngine#removeInjection} to remove it later on.
- */
- CIQ.ChartEngine.prototype.prepend = function (o, n) {
- var m = "prepend" + o;
- var prepends;
- if (this instanceof CIQ.ChartEngine) {
- prepends = this.hasOwnProperty(m) ? this[m] : [];
- this[m] = [n].concat(prepends);
- } else {
- prepends = CIQ.ChartEngine.prototype[m] || [];
- CIQ.ChartEngine.prototype[m] = [n].concat(prepends);
- }
- return { method: m, func: n };
- };
-
- /**
- * Appends custom developer functionality to an internal chart member. See [“Injection API"]{@tutorial Using the Injection API}.
- * @param {string} o Signature of member
- * @param {function} n Callback function, will be called with "apply"
- * @memberof CIQ.ChartEngine
- * @since
- * - 04-2015 You can append either to an {@link CIQ.ChartEngine} instance, or to the prototype. The first will affect only a single
- * chart while the latter will affect any chart (if you have multiple on the screen)
- * - 15-07-01 Function returns a descriptor which can be passed in to [removeInjection()]{@link CIQ.ChartEngine#removeInjection} to remove it later on.
- * @return {object} Injection descriptor which can be passed in to {@link CIQ.ChartEngine#removeInjection} to remove it later on.
- */
- CIQ.ChartEngine.prototype.append = function (o, n) {
- var m = "append" + o;
- var appends;
- if (this instanceof CIQ.ChartEngine) {
- appends = this.hasOwnProperty(m) ? this[m] : [];
- this[m] = appends.concat(n);
- } else {
- appends = CIQ.ChartEngine.prototype[m] || [];
- CIQ.ChartEngine.prototype[m] = appends.concat(n);
- }
- return { method: m, func: n };
- };
-
- /**
- * Runs the prepend injections. A prepend function that returns true will short circuit any proceeding prepend functions, and the core functionality.
- * @private
- * @param {string} o The function name
- * @param {Array} args The arguments to the function
- * @param {object} self The this object
- * @return {boolean} Returns true if any prepend function returns true.
- * @memberof CIQ.ChartEngine
- */
- CIQ.ChartEngine.prototype.runPrepend = function (o, args, self) {
- var n = "prepend" + o;
- var prepends = this.hasOwnProperty(n) ? this[n] : [];
- prepends = prepends.concat(CIQ.ChartEngine.prototype[n] || []);
- if (!prepends.length) return false;
- if (!self) self = this;
- for (var i = 0; i < prepends.length; i++) {
- var rv = prepends[i].apply(self, args);
- if (rv) return rv;
- }
- return false;
- };
-
- /**
- * Runs the append injections. An append function that returns true will short circuit any proceeding append functions (but not the core functionality since that has already ocurred).
- * @private
- * @param {string} o The function name
- * @param {Array} args The arguments to the function
- * @param {object} self The this object
- * @return {boolean} Returns true if any append function returns true.
- * @memberof CIQ.ChartEngine
- */
- CIQ.ChartEngine.prototype.runAppend = function (o, args, self) {
- var n = "append" + o;
- var appends = this.hasOwnProperty(n) ? this[n] : [];
- appends = appends.concat(CIQ.ChartEngine.prototype[n] || []);
- if (!appends.length) return false;
- if (!self) self = this;
- for (var i = 0; i < appends.length; i++) {
- var rv = appends[i].apply(self, args);
- if (rv) return rv;
- }
- return false;
- };
-
- /**
- * Removes a specific injection. One can remove either an instance injection or a prototype injection, depending on how the function is called.
- * @param {object} id The injection descriptor returned from {@link CIQ.ChartEngine#prepend} or {@link CIQ.ChartEngine#append}
- * @since 07/01/2015
- * @memberof CIQ.ChartEngine
- */
- CIQ.ChartEngine.prototype.removeInjection = function (id) {
- var method = id.method;
- var i;
- if (this instanceof CIQ.ChartEngine) {
- if (!this[method]) return;
- for (i = 0; i < this[method].length; i++) {
- if (this[method][i] == id.func) {
- this[method].splice(i, 1);
- return;
- }
- }
- } else {
- if (!CIQ.ChartEngine.prototype[method]) return;
- for (i = 0; i < CIQ.ChartEngine.prototype[method].length; i++) {
- if (CIQ.ChartEngine.prototype[method][i] == id.func) {
- CIQ.ChartEngine.prototype[method].splice(i, 1);
- return;
- }
- }
- }
- };
- /**
- * Removes any and all prepend and append injections from a specified CIQ.ChartEngine function.
- * If called as an instance method, will remove the instance injections.
- * If called as a prototype method, will remove the prototype injections.
- * @example
- * stxx.remove("displayChart"); // removes instance injections
- * CIQ.ChartEngine.prototpye.remove("displayChart"); // removes prototype injections
- * @param {string} o Signature of function which has injections to remove
- * @memberof CIQ.ChartEngine
- */
- CIQ.ChartEngine.prototype.remove = function (o) {
- if (this instanceof CIQ.ChartEngine) {
- delete this["append" + o];
- delete this["prepend" + o];
- } else {
- delete CIQ.ChartEngine.prototype["append" + o];
- delete CIQ.ChartEngine.prototype["prepend" + o];
- }
- };
-
- };
-
-
- let __js_core_engine_misc_ = (_exports) => {
-
-
- var CIQ = _exports.CIQ,
- timezoneJS = _exports.timezoneJS;
-
- /**
- * Given a browser time it will return the date in dataZone time. See {@link CIQ.ChartEngine#setTimeZone} for more details.
- * If no dataZone is set, it will return the original date passed in.
- * @param {date} browserDate Date in browser time - as in 'new Date();'
- * @return {date} Date converted to dataZone
- * @memberof CIQ.ChartEngine
- * @since 07-2016-16.6
- */
- CIQ.ChartEngine.prototype.convertToDataZone = function (browserDate) {
- if ((browserDate || browserDate === 0) && this.dataZone) {
- // convert the current time to the dataZone
- var tzNow = CIQ.convertTimeZone(browserDate, null, this.dataZone);
- // remember the the masterData is in local time but really representing the dataZone time.
- // now build a browser timezone time using the dataZone time so it will match the offset of the existing data in masterData.
- browserDate = new Date(
- tzNow.getFullYear(),
- tzNow.getMonth(),
- tzNow.getDate(),
- tzNow.getHours(),
- tzNow.getMinutes(),
- tzNow.getSeconds(),
- tzNow.getMilliseconds()
- );
- }
- return browserDate;
- };
-
- /**
- * This method does nothing. It is just a known location to put a break point for debugging the kernel.
- * @private
- */
- CIQ.ChartEngine.prototype.debug = function () {};
-
- /**
- * Measures frames per second. Use this from the console.
- * @param {number} [seconds = 5] Polling interval length
- * @param {function} cb Callback to invoke when done polling
- * @private
- */
- CIQ.ChartEngine.prototype.fps = function (seconds, cb) {
- seconds = seconds || 5;
- var start = new Date().getTime();
- var frames = 0;
- var self = this;
- console.log("Running fps() for " + seconds + " seconds");
-
- function render() {
- var duration = (new Date().getTime() - start) / 1000;
- if (duration > seconds) {
- var fps = frames / duration;
- console.log("FPS=" + fps);
- if (cb) cb(fps);
- return;
- }
- self.draw();
- frames++;
- if (CIQ.ChartEngine.useAnimation) {
- requestAnimationFrame(render);
- } else {
- setTimeout(render, 0);
- }
- }
- render();
- };
-
- // minimal here, see interaction file for complete list and docs
- CIQ.ChartEngine.htmlControls = {
- mSticky:
- '
- * Since 'tick' is not a time based display, there is no way to predict what the time between ticks will be.
- * Ticks can come a second later, a minute later or even more depending on how active a particular instrument may be.
- * As such, if iterating through the market day in 'tick' periodicity, the library uses a pre-defined number of minutes to move around.
- * This will be primarily used when deciding where to put x axis labels when going into the future in 'tick' mode.
- *
- * @type number
- * @default
- * @memberof CIQ.ChartEngine.XAxis#
- * @example
- * //You can override this behavior as follows:
- * var stxx=new CIQ.ChartEngine({container:document.querySelector(".chartContainer"), layout:{"candleWidth": 16, "crosshair":true}});
- * stxx.chart.xAxis.futureTicksInterval=1; // to set to 1 minute, for example
- * @since 3.0.0 Default changed from 10 to 1.
- */
- futureTicksInterval: 1
- },
- true
- );
-
- /**
- * This is the object stored in CIQ.ChartEngine.chart.xaxis array which contains information regarding an x-axis tick.
- * See {@link CIQ.ChartEngine.AdvancedInjectable#createXAxis} for more detail.
- * @constructor
- * @param {number} hz Horizontal position of center of label in pixels. Any elements with negative positions will be off the edge of the screen, and are only maintained to help produce a more predictable display as the chart is zoomed and paned.
- * @param {string} grid Either "line" or "boundary" depending on whether the label should be a date/time boundary or just a grid line
- * @param {string} text The text to display in the label
- * @name CIQ.ChartEngine.XAxisLabel
- */
- CIQ.ChartEngine.XAxisLabel = function (hz, grid, text) {
- this.hz = hz;
- this.grid = grid;
- this.text = text;
- };
-
- /**
- * INJECTABLE
- * Animation Loop
- *
- * Call this method to create the X axis (date axis). Uses {@link CIQ.ChartEngine#createTickXAxisWithDates}.
- *
- * Use css styles `stx_xaxis` to control colors and fonts for the dates. Example:
- *
- * Optionally you can [define an edit event listeners]{@link CIQ.ChartEngine#addEventListener} to call a custom function that can handle initialization of a dialog box for editing studies.
- * - Use [studyPanelEditEventListener]{@link CIQ.ChartEngine~studyPanelEditEventListener} to link the cog wheel on study panels to your desired edit menu/functionality.
- * - Use [studyOverlayEditEventListener]{@link CIQ.ChartEngine~studyOverlayEditEventListener} to link the right click on study overlays to your desired edit menu/functionality.
- * - All studies will use the same function set by the event listeners.
- * - If there are no event listeners set, the edit study buttons/functionality will not appear.
- * - The 'Study Edit' feature is standard functionality in the advanced sample template.
- * - See `Examples` section for exact function parameters and return value requirements. Note the convention to use sd.name+"_hist" for histogram values on a study
`gap` will cause physical breaks to occur on the chart in the gapped position.
- *
- * **Note:** the clean up process uses the active periodicity and the active market definition, if any.
- * So you must first set those to ensure proper clean up.
- * If no market definition is enabled, the clean up will assume gaps need to be added during the entire 24 hours period, every day.
- *
See "{@link CIQ.Market}" for details on how to properly configure the library to your market hours requirements.
- *
No gaps will be cleaned for `tick` since by nature it is no a predictable interval.
- *
- * **Important information to prevent inaccurate 'gapping'**
- * - This parameter must be set **before** any data is loaded into the chart.
- * - The cleanup process leverages the current market iterator which traverses along the timeline on the exact minute/second/millisecond mark for intraday data.
- * As such, you must ensure your time stamps match this requirement.
- * If your data does not comply, you must round your timestamps before sending the data into the chart.
- *
For example, if in minute periodicity, seconds and milliseconds should not be present or be set to zero.
- *
- * @type string
- * @default
- * @alias cleanupGaps
- * @memberof CIQ.ChartEngine.prototype
- *
- * @example
- * The following is an example for setting some of the available layout parameters:
- * ```
- * var stxx=new CIQ.ChartEngine({
- * container: document.querySelector(".chartContainer"),
- * layout:{
- * "crosshair":true,
- * "interval":"day",
- * "periodicity":1,
- * "chartType": "candle",
- * "candleWidth": 16
- * }
- * });
- * ```
- * These parameters will then be activated when [loadChart()]{@link CIQ.ChartEngine#loadChart} is called to render the chart.
- * Once a chart is rendered, most of these parameters become `READ ONLY`,and must be modified using their corresponding methods, as indicated in the documentation, to ensure chart integrity.
- *
- * **Important Note on internal periodicity format:**
- * Internal format of the layout object **does not match the parameters** used in {@link CIQ.ChartEngine#setPeriodicity} or {@link CIQ.ChartEngine#loadChart}.
- *
Use {@link CIQ.ChartEngine#getPeriodicity} to extract internal periodicity into the expected external format.
- *
- * See [importLayout]{@link CIQ.ChartEngine#importLayout} and [exportLayout]{@link CIQ.ChartEngine#exportLayout} for methods to serialize a layout and restore previously saved settings.
- *
- * @type object
- * @alias layout
- * @memberof CIQ.ChartEngine.prototype
- */
- layout: {
- /**
- * READ ONLY. Chart interval.
- *
- * Note that internal interval format will differ from API parameters used in {@link CIQ.ChartEngine#setPeriodicity} and {@link CIQ.ChartEngine#loadChart}.
- *
- * Available options are:
- * - [number] representing minutes
- * - "day"
- * - "week"
- * - "month"
- *
- * See the [Periodicity and Quote feed]{@tutorial Periodicity} tutorial.
- * @type string
- * @default
- * @alias CIQ.ChartEngine#layout[`interval`]
- * @memberof CIQ.ChartEngine.layout#
- */
- interval: "day",
- /**
- * READ ONLY. Number of periods per interval/timeUnit
- *
- * See the [Periodicity and Quote feed]{@tutorial Periodicity} tutorial.
- * @type number
- * @default
- * @alias CIQ.ChartEngine#layout[`periodicity`]
- * @memberof CIQ.ChartEngine.layout#
- */
- periodicity: 1,
- /**
- * READ ONLY. Time unit for the interval.
- *
- * Note that internal timeUnit format will differ from API parameters used in {@link CIQ.ChartEngine#setPeriodicity} and {@link CIQ.ChartEngine#loadChart}.
- *
- * See the [Periodicity and Quote feed]{@tutorial Periodicity} tutorial.
- * Available options are:
- * - "millisecond"
- * - "second"
- * - "minute"
- * - null for "day", "week", "month" periodicity
- * @type string
- * @default
- * @alias CIQ.ChartEngine#layout[`timeUnit`]
- * @memberof CIQ.ChartEngine.layout#
- */
- timeUnit: null,
- /**
- * READ ONLY. Candle Width In pixels ( see {@tutorial Understanding Chart Range} and {@link CIQ.ChartEngine#candleWidthPercent})
- * @type number
- * @default
- * @alias CIQ.ChartEngine#layout[`candleWidth`]
- * @memberof CIQ.ChartEngine.layout#
- */
- candleWidth: 8,
- /**
- * READ ONLY. The primary y-axis and all linked drawings, series and studies will display inverted (flipped) from its previous state when 'true'.
- *
- * Use {@link CIQ.ChartEngine#flipChart} to set.
- * @type boolean
- * @default
- * @alias CIQ.ChartEngine#layout[`flipped`]
- * @memberof CIQ.ChartEngine.layout#
- */
- flipped: false,
- volumeUnderlay: false,
- /**
- * Whether adjusted or nominal prices are being displayed.
- * If true then the chart will look for "Adj_Close" in the masterData as an alternative to "Close".
- * @type boolean
- * @default
- * @alias CIQ.ChartEngine#layout[`adj`]
- * @memberof CIQ.ChartEngine.layout#
- * @instance
- */
- adj: true,
- /**
- * Set to `true` to enable crosshairs in the active layout.
- *
- * Also see {@link CIQ.ChartEngine.AdvancedInjectable#doDisplayCrosshairs} for more details on crosshairs behavior.
- *
- * @example
- * // enable crosshair (usually called from a UI button/toggle)
- * stx.layout.crosshair=true;
- * // add this if you want the crosshair to display right away instead of when the user starts moving the mouse over the chart
- * stx.doDisplayCrosshairs();
- * // add this if you want to trigger a layout change event; maybe to save the layout.
- * stx.dispatch("layout", {stx:stx, symbol: stx.chart.symbol, symbolObject:stx.chart.symbolObject, layout:stx.layout, drawings:stx.drawingObjects});
- *
- * @type boolean
- * @default
- * @alias CIQ.ChartEngine#layout[`crosshair`]
- * @memberof CIQ.ChartEngine.layout#
- * @instance
- */
- crosshair: false,
- /**
- * READ ONLY. The primary chart type.
- *
- * Available options are:
- * - "none"
- * - "line"
- * - "step"
- * - "mountain"
- * - "baseline_delta"
- * - "candle"
- * - "bar"
- * - "hlc"
- * - "hlc_box" — Requires *js/extras/hlcbox.js*.
- * - "hlc_shaded_box" — Requires *js/extras/hlcbox.js*.
- * - "wave"
- * - "scatterplot"
- * - "histogram"
- * - "rangechannel"
- * - "marketdepth" — Requires the [Active Trader]{@link CIQ.MarketDepth} plug-in. See {@link CIQ.ChartEngine#updateCurrentMarketData} for data requirements.
- * - "termstructure" — Requires the [Term Structure]{@link CIQ.TermStructure} plug-in.
- *
- * Variations of these types are available by prepending terms to the options as follows:
- * - "step_" - add to mountain, marketdepth e.g. step_mountain, step_volume_marketdepth
- * - "vertex_" - add to line, step, mountain, baseline_delta
- * - "hollow_" - add to candle
- * - "volume_" - add to candle, marketdepth e.g. mountain_volume_marketdepth (Adding volume to marketdepth also creates a volume histogram in the same panel)
- * - "colored_" - add to line, mountain, step, bar, hlc
- * - "mountain_" - add to baseline_delta, marketdepth e.g. mountain_volume_marketdepth
- *
- * Other options are available provided a renderer is created with a `requestNew` function which will support the option, see {@link CIQ.Renderer.Lines#requestNew} and {@link CIQ.Renderer.OHLC#requestNew}
- *
- * Use {@link CIQ.ChartEngine#setChartType} to set this value.
- *
- * See {@tutorial Chart Styles and Types} for more details.
- *
- * @type string
- * @default
- * @alias CIQ.ChartEngine#layout[`chartType`]
- * @memberof CIQ.ChartEngine.layout#
- * @since
- * - 05-2016-10.1 Added "baseline_delta_mountain" and "colored_mountain".
- * - 3.0.0 Added "histogram" and "step".
- * - 3.0.7 Added "hlc".
- * - 4.0.0 Added "colored_step" and "colored_hlc".
- * - 5.1.0 More chart types available using combinations of terms.
- * - 6.1.0 Added "marketdepth".
- */
- chartType: "candle",
- /**
- * READ ONLY. Flag for extended hours time-frames.
- *
- * The chart includes the 'extended' parameter in the `params` object sent into the `fetch()` call.
- * Your quote feed must be able to provide extended hours data when requested (`extended:true`) for any extended hours functionality to work.
- *
- * See {@link CIQ.ExtendedHours} and {@link CIQ.Market} for more details on how extended hours are set and used.
- * @type boolean
- * @default
- * @alias CIQ.ChartEngine#layout[`extended`]
- * @memberof CIQ.ChartEngine.layout#
- */
- extended: false,
- /**
- * READ ONLY. Tracks the extended market sessions to display on the chart.
- *
- * See {@link CIQ.ExtendedHours} and {@link CIQ.Market} for more details on how extended hours are set and used.
- * @type object
- * @default
- * @alias CIQ.ChartEngine#layout[`marketSessions`]
- * @memberof CIQ.ChartEngine.layout#
- * @example
- * marketSessions = {
- * "session1": true,
- * "session2": true,
- * "session3": false,
- * "pre": true,
- * "post": true
- * }
- * @since 06-2016-02
- */
- marketSessions: {}, //use defaults
- /**
- * READ ONLY. Active aggregation for the chart.
- *
- * Available options are:
- * - "rangebars"
- * - "ohlc"
- * - "kagi"
- * - "pandf"
- * - "heikinashi"
- * - "linebreak"
- * - "renko"
- *
- * Use {@link CIQ.ChartEngine#setAggregationType} to set this value.
- *
- * See {@tutorial Chart Styles and Types} for more details.
- * @type string
- * @default
- * @alias CIQ.ChartEngine#layout[`aggregationType`]
- * @memberof CIQ.ChartEngine.layout#
- */
- aggregationType: "ohlc",
- /**
- * READ ONLY. Active scale for the chart.
- *
- * See {@link CIQ.ChartEngine#setChartScale}
- *
- * **Replaces CIQ.ChartEngine.layout.semiLog**
- *
- * @type string
- * @default
- * @alias CIQ.ChartEngine#layout[`chartScale`]
- * @memberof CIQ.ChartEngine.layout#
- */
- chartScale: "linear",
- /**
- * READ ONLY. List of [study descriptors]{@link CIQ.Studies.StudyDescriptor} for the active studies on the chart.
- *
- * **Please note:** To facilitate study name translations, study names use zero-width non-joiner (unprintable) characters to delimit the general study name from the specific study parameters.
- * Example: "\u200c"+"Aroon"+"\u200c"+" (14)".
- * At translation time, the library will split the text into pieces using the ZWNJ characters, parentheses and commas to just translate the required part of a study name.
- * For more information on ZWNJ characters see: [Zero-width_non-joiner](https://en.wikipedia.org/wiki/Zero-width_non-joiner).
- * Please be aware of these ZWNJ characters, which will now be present in all study names and corresponding panel names; including the `layout.studies` study keys.
- * Affected fields in the study descriptors could be `id `, `display`, `name` and `panel`.
- *
To prevent issues, always use the names returned in the **study descriptor**. This will ensure compatibility between versions.
- * >Example:
- * >
Correct reference:
- * >
`stxx.layout.studies["\u200c"+"Aroon"+"\u200c"+" (14)"];`
- * >
Incorrect reference:
- * >
`stxx.layout.studies["Aroon (14)"];`
- *
- * See {@link CIQ.Studies.addStudy} for more details
- *
- * @type object
- * @default
- * @alias CIQ.ChartEngine#layout[`studies`]
- * @memberof CIQ.ChartEngine.layout#
- */
- studies: {},
- /**
- * READ ONLY. List of active chart panels. Usually correspond to a study or series.
- *
- * **Please note:** To facilitate study name translations, study names and their corresponding panels use zero-width non-joiner (unprintable) characters to delimit the general study name from the specific study parameters.
- * Example: "\u200c"+"Aroon"+"\u200c"+" (14)".
- * At translation time, the library will split the text into pieces using the ZWNJ characters, parentheses and commas to just translate the required part of a study name.
- * For more information on ZWNJ characters see: [Zero-width_non-joiner](https://en.wikipedia.org/wiki/Zero-width_non-joiner).
- * Please be aware of these ZWNJ characters, which will now be present in all study names and corresponding panel names; including the `layout.panels` study keys.
- *
To prevent issues, always use the names returned in the **study descriptor**. This will ensure compatibility between versions.
- * >Example:
- * >
Correct reference:
- * >
`stxx.layout.panels["\u200c"+"Aroon"+"\u200c"+" (14)"];`
- * >
Incorrect reference:
- * >
`stxx.layout.panels["Aroon(14)"];`
- *
- * See {@link CIQ.Studies.addStudy} for more details
- *
- * @type object
- * @default
- * @alias CIQ.ChartEngine#layout[`panels`]
- * @memberof CIQ.ChartEngine.layout#
- */
- panels: {},
- setSpan: {},
- /**
- * READ ONLY. Specifies whether outlier detection is enabled. A value of true enables
- * detection; false disables detection.
- *
- * See {@link CIQ.Outliers} for information on how outlier detection is used.
- *
- * @type boolean
- * @default
- * @alias CIQ.ChartEngine#layout[`outliers`]
- * @memberof CIQ.ChartEngine.layout#
- * @since 7.5.0
- */
- outliers: false
- },
- /**
- * Contains the chart preferences.
- *
- * Preferences parameters, unless otherwise indicated, can be set at any time and only require a [draw()]{@link CIQ.ChartEngine#draw} call to activate.
- *
- * See [importPreferences]{@link CIQ.ChartEngine#importPreferences} and [exportPreferences]{@link CIQ.ChartEngine#exportPreferences} for methods to serialize and restore previously saved preferences.
- *
- * @type object
- * @alias preferences
- * @memberof CIQ.ChartEngine.prototype
- */
- preferences: {
- /**
- * Draw a horizontal line at the current price.
- * Only drawn if the most recent tick is visible.
- *
- * See {@link CIQ.ChartEngine.AdvancedInjectable#drawCurrentHR}
- *
- * @type boolean
- * @default
- * @alias CIQ.ChartEngine#preferences[`currentPriceLine`]
- * @memberof CIQ.ChartEngine.preferences#
- * @since 05-2016-10
- */
- currentPriceLine: false,
- /**
- * Disables dragging a plot between panels or a y-axis within a panel.
- * Separate switches are provided for dragging studies, series, or axes.
- * Alternatively, all dragging may be disabled by setting `dragging: false`.
- *
- * To also disable the highlight when hovering over the Y axis, add the following:
- * ```
- * CIQ.ChartEngine.YAxis.prototype.setBackground = function() {}
- * ```
- *
- * To also disable the highlight when hovering over the Y axis, add the following:
- * ```
- * CIQ.ChartEngine.YAxis.prototype.setBackground = function() {}
- * ```
- *
- * @type object|boolean
- * @default
- * @alias CIQ.ChartEngine#preferences[`dragging`]
- * @memberof CIQ.ChartEngine.preferences#
- * @since 7.1.0
- * @example
- * stxx.preferences.dragging.study=false;
- * @example
- * stxx.preferences.dragging=false;
- */
- dragging: {
- series: true,
- study: true,
- yaxis: true
- },
- /**
- * When using drawing tools, this will become an object when user saves the drawing parameters.
- * A sub-object is created for each drawing tool.
- * These preferences are used whenever the user selects that drawing object, and overrides the default stxx.currentVectorParameters.
- * Use {@link CIQ.Drawing.saveConfig} to save the parameters to this object.
- * @type object
- * @default
- * @alias CIQ.ChartEngine#preferences[`drawings`]
- * @memberof CIQ.ChartEngine.preferences#
- * @since 6.0.0
- */
- drawings: null,
- /**
- * Pixel radius for the invisible intersection box around the cursor used to determine if it has intersected with an element to be highlighted.
- * This value is used primarily for non-touch cursor events (mouse, touchpad). Used on items removed with a right click such as series and drawings.
- *
- * Only applicable if the user has **not** tapped on the screen to set the location of the cross-hair.
- *
- * @type number
- * @default
- * @alias CIQ.ChartEngine#preferences[`highlightsRadius`]
- * @memberof CIQ.ChartEngine.preferences#
- * @since 3.0.0
- */
- highlightsRadius: 10,
- /**
- * For touch events on the chart canvas. Pixel radius for the invisible intersection box around the cursor used to determine if it has intersected
- * with an element to be highlighted. The larger highlight radius is more suitable for the less precise input from touch events. Used on
- * items removed with a right click such as series and drawings.
- *
- * **Only applicable for touch events while the cursor is not controlling the crosshair tool. Otherwise, highlightsRadius is used.**
- *
- * @type number
- * @default
- * @alias CIQ.ChartEngine#preferences[`highlightsTapRadius`]
- * @memberof CIQ.ChartEngine.preferences#
- * @since 3.0.0
- */
- highlightsTapRadius: 30,
- /**
- * Magnetizes the crosshairs to data points during drawing operations to improve initial placement accuracy.
- *
- * - When `true`, the magnet is considered "strong" and will always magnetize.
- * - When a number, it is considered "weak" and will only magnetize within the area of defined. The radius of the circle is the number you set.
- *
- * **We recommend 75 as the value for the parameter when the `number` type is used.**
- *
- * It will not be used when an existing drawing is being repositioned.
- *
- * See {@link CIQ.ChartEngine.AdvancedInjectable#magnetize} for more details.
- *
- * @type boolean | number
- * @default
- * @alias CIQ.ChartEngine#preferences[`magnet`]
- * @memberof CIQ.ChartEngine.preferences#
- * @since 7.2.0 Magnets can now be applied to any series or study.
- */
- magnet: false,
- /**
- * Locks the crosshair y-coordinate to the value of the field name specified for the tick under the cursor on the primary chart.
- *
- * For studies, create a `horizontalCrosshairFieldFN` function that will be called by `CIQ.Studies.addStudy`.
- * The function must return the field name in the data set to reference. The function will not be called when the study is set to
- * overlay or underlay the chart's panel.
- *
- * @example
- * // Have the crosshairs lock to the "Close" field of the tick under the cursor.
- * stxx.preferences.horizontalCrosshairField = "Close";
- *
- * @example
- * // Have the crosshair slock to the "ATR ATR (14)" field for a ATR study with a period of 14.
- * CIQ.Studies.studyLibrary["ATR"].horizontalCrosshairFieldFN = function(stx, sd) {
- * // Returns the field name, which should be created by the study's "calculateFN".
- * return "ATR " + sd.name;
- * };
- *
- * @type string
- * @default
- * @alias CIQ.ChartEngine#preferences[`horizontalCrosshairField`]
- * @memberof CIQ.ChartEngine.preferences#
- * @since 04-2016-08
- */
- horizontalCrosshairField: null,
- /**
- * Set to true to display labels on y-axis for line based studies using {@link CIQ.Studies.displayIndividualSeriesAsLine} or {@link CIQ.Studies.displaySeriesAsLine} (this is overridden by the particular y-axis setting of {@link CIQ.ChartEngine.YAxis#drawPriceLabels}).
- * This flag is checked inside these 2 functions to decide if a label should be set, as such if you do not wish to have a label on a particular study line, you can set this flag to `false`, before calling the function, and then back to `true`.
- * @type boolean
- * @default
- * @alias CIQ.ChartEngine#preferences[`labels`]
- * @memberof CIQ.ChartEngine.preferences#
- * @example
- * //do not display the price labels for this study
- * stxx.preferences.labels=false;
- * CIQ.Studies.displaySeriesAsLine(stx, sd, quotes);
- *
- * //restore price labels to default value
- * stxx.preferences.labels=true;
- */
- labels: true,
- /**
- * Stores preferred language for the chart.
- *
- * It can be individually restored using {@link CIQ.I18N.setLanguage} and activated by {@link CIQ.I18N.translateUI}
- * @type {string}
- * @alias CIQ.ChartEngine#preferences[`language`]
- * @memberof CIQ.ChartEngine.preferences#
- * @since 4.0.0
- */
- language: null,
- /**
- * Stores the preferred timezone for the display of the x axis labels.
- *
- * It is automatically set and can be individually restored by {@link CIQ.ChartEngine#setTimeZone}.
- * @type {string}
- * @alias CIQ.ChartEngine#preferences[`timezone`]
- * @memberof CIQ.ChartEngine.preferences#
- * @since 4.0.0
- */
- timeZone: null,
- /**
- * Initial whitespace on right of the screen in pixels.
- * @type number
- * @default
- * @alias CIQ.ChartEngine#preferences[`whitespace`]
- * @memberof CIQ.ChartEngine.preferences#
- * @example
- * // override the default value at declaration time
- * var stxx=new CIQ.ChartEngine({container:document.querySelector(".chartContainer"), preferences:{"whitespace": 20}});
- */
- whitespace: 50,
- /**
- * zoom-in speed for mousewheel and zoom button.
- *
- * Range: **0 -.99999**. The closer to 1 the slower the zoom.
- * @type number
- * @default
- * @alias CIQ.ChartEngine#preferences[`zoomInSpeed`]
- * @memberof CIQ.ChartEngine.preferences#
- * @example
- * stxx.preferences.zoomInSpeed=.91;
- * @example
- * var stxx=new CIQ.ChartEngine({container:document.querySelector(".chartContainer"), preferences:{"zoomInSpeed": .98}});
- * @since 07/01/2015
- */
- zoomInSpeed: null,
- /**
- * zoom-out speed for mousewheel and zoom button.
- *
- * Range: **1-2**. The closer to 1 the slower the zoom.
- * @type number
- * @default
- * @alias CIQ.ChartEngine#preferences[`zoomOutSpeed`]
- * @memberof CIQ.ChartEngine.preferences#
- * @example
- * stxx.preferences.zoomOutSpeed=1.1;
- * @example
- * var stxx=new CIQ.ChartEngine({container:document.querySelector(".chartContainer"), preferences:{"zoomOutSpeed": 1}});
- * @since 07/01/2015
- */
- zoomOutSpeed: null,
- /**
- * If set to 'true', the mouse wheel zooming is centered by the mouse position.
- *
- * @type boolean
- * @default
- * @alias CIQ.ChartEngine#preferences[`zoomAtCurrentMousePosition`]
- * @memberof CIQ.ChartEngine.preferences#
- * @since 4.0.0
- */
- zoomAtCurrentMousePosition: false
- },
- /**
- * Used to control the behavior and throttling of real time updates in [updateChartData()]{@link CIQ.ChartEngine#updateChartData} to prevent overloading the chart engine
- * @type object
- * @alias streamParameters
- * @memberof CIQ.ChartEngine.prototype
- * @example
- * // this will cause updates to be applied to the dataSegment immediately
- * stxx.streamParameters.maxTicks=0;
- *
- * // here is how you would override all options
- * stxx.streamParameters= {"maxWait":1000,"maxTicks":100}
- */
- streamParameters: {
- count: 0,
- /**
- * ms to wait before allowing update to occur (if this condition is met, the update will occur and all pending ticks will be loaded - exclusive of maxTicks)
- * @type number
- * @default
- * @alias CIQ.ChartEngine#streamParameters[`maxWait`]
- * @memberof CIQ.ChartEngine.streamParameters#
- * @example
- * // update without any time interval delay.
- * stxx.streamParameters.maxWait=0;
- */
- maxWait: 1000,
- /**
- * ticks to wait before allowing update to occur (if this condition is met, the update will occur and all pending ticks will be loaded - exclusive of maxWait)
- * @type number
- * @default
- * @alias CIQ.ChartEngine#streamParameters[`maxTicks`]
- * @memberof CIQ.ChartEngine.streamParameters#
- * @example
- * // update with every new tick added.
- * stxx.streamParameters.maxTicks=0;
- */
- maxTicks: 100,
- timeout: -1
- },
- /**
- * Allow the candle width to be determined dynamically when using {@link CIQ.ChartEngine#setRange}.
- * This will require a valid {@link CIQ.ChartEngine#dynamicRangePeriodicityMap}
- * @type object
- * @default
- * @alias autoPickCandleWidth
- * @memberof CIQ.ChartEngine.prototype
- * @example
- * autoPickCandleWidth:{
- * turnOn: true,
- * candleWidth: 5
- * }
- * @since m-2016-12-01
- */
- autoPickCandleWidth: {
- /**
- * Turn to 'true' if you want the periodicity to be determined dynamically when using {@link CIQ.ChartEngine#setRange}.
- * This will require a valid {@link CIQ.ChartEngine#dynamicRangePeriodicityMap}
- * @type boolean
- * @default
- * @alias CIQ.ChartEngine#autoPickCandleWidth[`turnOn`]
- * @memberof CIQ.ChartEngine.autoPickCandleWidth#
- */
- turnOn: false,
-
- /**
- * Set if you want to set a specific candle width when using {@link CIQ.ChartEngine#setRange}.
- * This will require a valid {@link CIQ.ChartEngine#dynamicRangePeriodicityMap}.
- * Set to '0' if you want the candle width to be determined according to chart type
- * @type number
- * @default
- * @alias CIQ.ChartEngine#autoPickCandleWidth[`candleWidth`]
- * @memberof CIQ.ChartEngine.autoPickCandleWidth#
- */
- candleWidth: 5
- }
- };
-
- CIQ.extend(CIQ.ChartEngine.prototype, prototypeSwitches);
-
- // Constant bitmask for bar evaluation
- CIQ.ChartEngine.NONE = 0; // no evaluation (black bars)
- CIQ.ChartEngine.CLOSEUP = 1; // today's close greater than yesterday's close
- CIQ.ChartEngine.CLOSEDOWN = 2; // today's close less than yesterday's close
- CIQ.ChartEngine.CLOSEEVEN = 4; // today's close the same as yesterday's close
- CIQ.ChartEngine.CANDLEUP = 8; // today's close greater than today's open
- CIQ.ChartEngine.CANDLEDOWN = 16; // today's close less than today's open
- CIQ.ChartEngine.CANDLEEVEN = 32; // today's close equal to today's open
-
- };
-
-
- let __js_core_formatData_ = (_exports) => {
-
-
- var CIQ = _exports.CIQ;
-
- /**
- * Converts a future month to the month index or vice versa. Month indexes begin with 1 for
- * January.
- *
- * @param {string} x The value to convert. If numeric, converted to future month letter. If
- * alphabetical, converted to month index.
- * @return {string} The converted value.
- *
- * @memberof CIQ
- */
- CIQ.convertFutureMonth = function (x) {
- var y = x.toString();
- if (y.length <= 0 || y.length > 2) return "";
- switch (y) {
- case "1":
- return "F";
- case "2":
- return "G";
- case "3":
- return "H";
- case "4":
- return "J";
- case "5":
- return "K";
- case "6":
- return "M";
- case "7":
- return "N";
- case "8":
- return "Q";
- case "9":
- return "U";
- case "10":
- return "V";
- case "11":
- return "X";
- case "12":
- return "Z";
- case "F":
- return "1";
- case "G":
- return "2";
- case "H":
- return "3";
- case "J":
- return "4";
- case "K":
- return "5";
- case "M":
- return "6";
- case "N":
- return "7";
- case "Q":
- return "8";
- case "U":
- return "9";
- case "V":
- return "10";
- case "X":
- return "11";
- case "Z":
- return "12";
- }
- return y;
- };
-
- /**
- * Prints out a number in US Dollar monetary representation
- * @param {number} val The amount
- * @param {number} [decimals=2] Number of decimal places.
- * @param {string} [currency] Currency designation. If omitted, will use $.
- * @return {string} US Dollar monetary representation
- * // Returns $100.00
- * CIQ.money(100, 2);
- * @memberof CIQ
- */
- CIQ.money = function (val, decimals, currency) {
- if (!currency) currency = "$";
- if (currency.length == 3) currency += " ";
- if (!decimals && decimals !== 0) decimals = 2;
- return (
- currency + CIQ.commas((Math.round(val * 10000) / 10000).toFixed(decimals))
- );
- };
-
- /**
- * Converts a currency code from ISO to char
- * @param {string} code The string to convert, e.g. USD
- * @return {string} The converted string, e.g. $
- * @memberof CIQ
- */
- CIQ.convertCurrencyCode = function (code) {
- var codes = {
- JPY: "¥",
- USD: "$",
- AUD: "A$",
- BRL: "R$",
- CAD: "CA$",
- CNY: "CN¥",
- CZK: "Kč",
- DKK: "kr",
- EUR: "€",
- GBP: "£",
- HKD: "HK$",
- HUF: "Ft",
- ILS: "₪",
- INR: "₹",
- KRW: "₩",
- MXN: "MX$",
- NOK: "kr",
- NZD: "NZ$",
- PLN: "zł",
- RUB: "руб",
- SAR: "﷼",
- SEK: "kr",
- SGD: "S$",
- THB: "฿",
- TRY: "₺",
- TWD: "NT$",
- VND: "₫",
- XAF: "FCFA",
- XCD: "EC$",
- XOF: "CFA",
- XPF: "CFPF",
- ZAR: "R"
- };
- var rt = codes[code];
- if (rt) return rt;
- return code;
- };
-
- /**
- * Returns a string representation of a number with commas in thousands, millions or billions places. Note that this function does
- * not handle values with more than 3 decimal places!!!
- * @param {number} val The value
- * @return {string} The result with commas
- * @example
- * // Returns 1,000,000
- * CIQ.commas(1000000);
- * @memberof CIQ
- */
- CIQ.commas = function (val) {
- return val.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
- };
-
- /**
- * Convenience function to convert API periodicity parameters to internal periodicity format.
- * @param {string} period The period value as required by {@link CIQ.ChartEngine#setPeriodicity}
- * @param {string} [interval] The interval value as required by {@link CIQ.ChartEngine#setPeriodicity}
- * @param {string} timeUnit The timeUnit value as required by {@link CIQ.ChartEngine#setPeriodicity}
- * @return {object} object containing internally compliant periodicity,interval, timeUnit
- * @memberof CIQ
- * @tsdeclaration
- * function cleanPeriodicity(period: number, interval: number, timeUnit: string)
- * function cleanPeriodicity(period: number, timeUnit: string)
- * @since 5.1.1
- */
- CIQ.cleanPeriodicity = function (period, interval, timeUnit) {
- if (isNaN(period)) period = 1;
- if (!interval) interval = 1;
- if (!isNaN(interval) && timeUnit) {
- // disregard the numeric interval if a daily timeUnit is provided
- // we'll need to propagate timeUnit down wherever we are examining the interval alone to determine the time unit
- if (
- !(
- timeUnit == "hour" ||
- timeUnit == "minute" ||
- timeUnit == "second" ||
- timeUnit == "millisecond"
- )
- ) {
- interval = timeUnit;
- timeUnit = null;
- }
- }
-
- // clean up timeUnit
- //if(CIQ.ChartEngine.isDailyInterval(interval)) timeUnit=null; // redundant
- else if (interval == "tick") timeUnit = null;
- else if (!timeUnit && !isNaN(interval)) timeUnit = "minute";
-
- // support hour
- if (timeUnit == "hour") {
- timeUnit = "minute";
- interval = interval * 60;
- }
-
- // support year
- if (interval == "year") {
- interval = "month";
- if (!period) period = 1;
- period = period * 12;
- }
-
- return { period: period, interval: interval, timeUnit: timeUnit };
- };
-
- /**
- * Creates a string with a periodicity that is easy to read given a chart
- * @param {CIQ.ChartEngine} stx A chart object
- * @return {string} A periodicity value that can be displayed to an end user
- * @memberof CIQ
- */
- CIQ.readablePeriodicity = function (stx) {
- var displayPeriodicity = stx.layout.periodicity;
- var displayInterval = stx.layout.interval;
- if (typeof stx.layout.interval == "number" && stx.layout.timeUnit) {
- displayPeriodicity = stx.layout.interval * stx.layout.periodicity;
- displayInterval = stx.layout.timeUnit;
- } else if (typeof stx.layout.interval == "number" && !stx.layout.timeUnit) {
- displayPeriodicity = stx.layout.interval * stx.layout.periodicity;
- displayInterval = "minute";
- }
- if (displayPeriodicity % 60 === 0 && displayInterval == "minute") {
- displayPeriodicity /= 60;
- displayInterval = "hour";
- }
- return (
- displayPeriodicity + " " + stx.translateIf(CIQ.capitalize(displayInterval))
- );
- };
-
- /**
- * Given a numeric price that may be a float with rounding errors, this will trim off the trailing zeroes
- * @param {number} price The price
- * @return {number} The price trimmed of trailing zeroes
- * @memberof CIQ
- */
- CIQ.fixPrice = function (price) {
- if (!price && price !== 0) return null;
- var p = price.toFixed(10);
- for (var i = p.length - 1; i > 1; i--) {
- if (p.charAt(i) != "0") break;
- }
- p = p.substring(0, i + 1);
- return parseFloat(p);
- };
-
- /**
- * Condenses a number into abbreviated form by adding "k","m","b" or "t".
- * This method is used in the y-axis for example with volume studies.
- * @param {number} txt - A numerical value
- * @return {string} Condensed version of the number if over 999, otherwise returns `txt` untouched
- * @example
- * // This will return 12m
- * condenseInt(12000000);
- * @memberof CIQ
- * @since 4.0.0 Now returns `txt` untouched if under 1000. Previously was removing all decimal places.
- */
- CIQ.condenseInt = function (txt) {
- if (txt === null || typeof txt == "undefined") return "";
- if (txt === Infinity || txt === -Infinity) return "n/a";
- var isNegative = txt < 0;
-
- if (!isNaN(txt)) {
- txt = Math.abs(txt);
-
- if (txt > 1000000000000) txt = Math.round(txt / 100000000000) / 10 + "t";
- else if (txt > 100000000000) txt = Math.round(txt / 1000000000) + "b";
- //100b
- else if (txt > 10000000000)
- txt = (Math.round(txt / 100000000) / 10).toFixed(1) + "b";
- //10.1b
- else if (txt > 1000000000)
- txt = (Math.round(txt / 10000000) / 100).toFixed(2) + "b";
- //1.11b
- else if (txt > 100000000) txt = Math.round(txt / 1000000) + "m";
- //100m
- else if (txt > 10000000)
- txt = (Math.round(txt / 100000) / 10).toFixed(1) + "m";
- //10.1m
- else if (txt > 1000000)
- txt = (Math.round(txt / 10000) / 100).toFixed(2) + "m";
- //1.11m
- else if (txt > 100000) txt = Math.round(txt / 1000) + "k";
- //100k
- else if (txt > 10000) txt = (Math.round(txt / 100) / 10).toFixed(1) + "k";
- //10.1k
- else if (txt > 1000) txt = (Math.round(txt / 10) / 100).toFixed(2) + "k";
- //1.11k
- else txt = txt.toString();
- } else {
- txt = txt.toString();
- }
-
- if (isNegative) txt = "-" + txt;
- return txt;
- };
-
- /**
- * Determines how many decimal places the security trades.
- *
- * This is the default calculateTradingDecimalPlaces function. It is used by {@link CIQ.ChartEngine#setMasterData} to round off the prices
- * to an appropriate number of decimal places. The function is assigned to {@link CIQ.ChartEngine.Chart#calculateTradingDecimalPlaces}},
- * but you may set to your own logic instead.
- *
- * The default algorithm is to check the most recent 50 quotes and find the maximum number of decimal places that the stock has traded.
- * This will work for most securities but if your market data feed has rounding errors
- * or bad data then you may want to supplement this algorithm that checks the maximum value by security type.
- *
- * It defaults to a minimum of 2 decimals.
- * @param {object} params Parameters
- * @param {CIQ.ChartEngine} params.stx The chart object
- * @param {CIQ.ChartEngine.Chart} params.chart The chart in question
- * @param {string} params.symbol The symbol string
- * @param {object} params.symbolObject The symbol object. If you create charts with just stock symbol then symbolObject.symbol will contain that symbol
- * @return {number} The number of decimal places
- * @memberof CIQ
- * @example
- * //set your own logic for calculating decimal places.
- * var stxx=new CIQ.ChartEngine({container:document.querySelector(".chartContainer"), preferences:{labels:false, currentPriceLine:true, whitespace:0}});
- * stxx.chart.calculateTradingDecimalPlaces=yourCustomFunction;
- * @example
- // default code
- CIQ.calculateTradingDecimalPlaces=function(params){
- var chart=params.chart;
- var decimalPlaces=2;
- var quotesToCheck = 50; // Check up to 50 recent quotes
- var masterData=chart.masterData;
- if(masterData && masterData.length > quotesToCheck){
- // exclude the current quote by setting i=2 in case animation is enabled. Animation uses very large decimals to allow for smooth movements.
- for(var i=2;i
If the data object contains a 'Value' field, this parameter will not be honored and instead the 'Value' field will be used as follows: masterData[mIterator][label] = data[dIterator]["Value"];
- * @param {string} [params.fieldForLabel="Close"] If set, this will be the field from each incoming data object that will be copied into the new label member in each masterData object. If not set, "Close" or "Value" is used, whichever exists; and if neither exists, it will attempt to copy over a field matching the `label` name. Example: masterData[mIterator][label]=data[dIterator][fieldForLabel]. This behavior is mutually exclusive with `fields` and `createObject`.
- * @param {boolean} [params.fillGaps] If true then gaps in data will be filled by carrying forward the value of from the previous bar.
- * @param {boolean} [params.noCleanupDates] If true then dates have been cleaned up already by calling {@link CIQ.ChartEngine#doCleanupDates}, so do not do so in here.
- * @param {CIQ.ChartEngine.Chart} [params.chart] The chart to update
- * @memberof CIQ
- * @example
- * //data element format if neither fields, fieldForLabel or createObject are used
- * {DT:epoch,Date:strDate,Value:value}
- * {DT:epoch,Date:strDate,Close:value }
- * //data element format if fields is used
- * {DT:epoch,Date:strDate,Field1:value,Field2:value,Field3:value,Field4:value}
- * //data element format if createObject is used
- * {DT:epoch,Date:strDate,AnyOtherData:otherData,MoreData:otherData,...}
- * @since
- * - 04-2015
- * - 15-07-01 Added `fieldForLabel` argument.
- * - 3.0.0 All data sent in will be forced into the chart. Dates are no longer required to be exact matches (minutes, hours, seconds, milliseconds) in order to show up in comparisons.
- * - 4.0.0 Arguments are now parameterized. Backward compatibility with old signature.
- * - 4.0.0 Added ability to specify copying of all fields by setting `params.fields=["*"]`.
- * - 5.2.0 Enhanced parameter `createObject` to take a string.
- * - 5.2.0 Added parameter `noCleanupDates`.
- */
- CIQ.addMemberToMasterdata = function (params) {
- if (params.constructor === CIQ.ChartEngine) {
- params = {
- stx: arguments[0],
- label: arguments[1],
- data: arguments[2],
- fields: arguments[3],
- createObject: arguments[4],
- fieldForLabel: arguments[5]
- };
- }
- var stx = params.stx;
- var label = params.label;
- var data = params.data;
- var fields = params.fields;
- var createObject = params.createObject;
- var fieldForLabel = params.fieldForLabel;
-
- var chart = params.chart ? params.chart : stx.chart;
-
- if (!params.noCleanupDates) stx.doCleanupDates(data, stx.layout.interval);
-
- var series = [];
- if (stx.getSeries) series = stx.getSeries({ symbol: label, chart: chart });
-
- if (data && data.constructor == Object) data = [data]; // When developer mistakenly sends an object instead of an array of objects
- if (!data || !data.length) return;
-
- var mIterator = 0,
- cIterator = 0,
- masterData = chart.masterData,
- layout = stx.layout,
- m,
- c;
- if (!masterData) {
- masterData = [];
- }
-
- var defaultPlotField = (chart && chart.defaultPlotField) || null;
- var isLineType =
- stx.mainSeriesRenderer && !stx.mainSeriesRenderer.highLowBars;
- var chartType = layout.chartType;
- if (!isLineType && chartType) {
- var renderer = CIQ.Renderer.produce(chartType, {});
- if (renderer) isLineType = !renderer.highLowBars;
- }
-
- function aggregate(m, c) {
- if (!m || typeof m != "object") {
- m = c;
- return m;
- }
- var prior = {
- Close: m.Close,
- High: m.High,
- Low: m.Low,
- Open: m.Open,
- Volume: m.Volume
- };
- m = c;
- for (var p in prior) {
- if (m.Close === null) {
- // Close is not set, nothing else is valid (it's a gap)
- if (m[p] !== undefined) m[p] = null;
- } else if (typeof m[p] !== "number") m[p] = prior[p];
- // new data invalid, use original data
- else if (typeof prior[p] === "number") {
- // aggregate the data
- if (p == "Open") m.Open = prior.Open;
- else if (p == "Low" && m.Low > prior.Low) m.Low = prior.Low;
- else if (p == "High" && m.High < prior.High) m.High = prior.High;
- else if (p == "Volume") m.Volume += prior.Volume;
- }
- }
- return m;
- }
-
- // inject data from c into m
- function injectData(m, c) {
- if (fields && fields.length) {
- // Case 1, copy the [several] specified fields from new object to masterData object
- if (fields[0] == "*") {
- // copy all fields
- CIQ.extend(m, c);
- } else {
- for (var i = 0; i < fields.length; i++) {
- m[fields[i]] = c[fields[i]];
- }
- }
- } else if (createObject) {
- // Case 2, the new object will become a child object of the masterData object
- if (c.Value !== undefined) {
- // If "Value" is in the new object use that
- m[label] = c.Value;
- return;
- } else if (createObject == "aggregate") {
- m[label] = aggregate(m[label], c);
- } else {
- m[label] = c;
- }
- // If we don't set this here, the study calculations will fail
- var m_ = m[label];
- if (typeof m_.Close == "number") {
- if (typeof m_.Open != "number") m_.Open = m_.Close;
- var high = Math.max(m_.Open, m_.Close),
- low = Math.min(m_.Open, m_.Close);
- if (typeof m_.High != "number" || m_.High < high) m_.High = high;
- if (typeof m_.Low != "number" || m_.Low > low) m_.Low = low;
- }
- if (m_.Volume && typeof m_.Volume !== "number")
- m_.Volume = parseInt(m_.Volume, 10);
- } else if (fieldForLabel) {
- // Case 3, copy the data from one label (fieldForLabel) to another (label)
- m[label] = c[fieldForLabel];
- } else if (
- isLineType &&
- defaultPlotField &&
- c[defaultPlotField] !== undefined
- ) {
- // If a default field on the chart has been provided, then use that if it's in the new object
- m[label] = c[defaultPlotField];
- } else if (layout.adj && c.Adj_Close !== undefined) {
- // If Adjusted close is in the new object, use that
- m[label] = c.Adj_Close;
- } else if (c.Close !== undefined) {
- // If Close is in the new object use that
- m[label] = c.Close;
- } else if (c.Value !== undefined) {
- // If "Value" is in the new object use that
- m[label] = c.Value;
- } else {
- // Default to copying the same label from the old to the new object.
- m[label] = c[label];
- }
- }
-
- // Binary search for next relevant masterData record, with the following modifications:
- // 1. Always check the very next record, since that is most likely
- // 2. Before search, check last record
- function fastSeek(date) {
- function testIt() {
- if (+masterData[mIterator].DT == +date) return 0;
- if (masterData[mIterator].DT < date) return 1;
- if (masterData[mIterator - 1].DT > date) return -1;
- if (+masterData[mIterator - 1].DT == +date) mIterator--; // efficiency
- return 0;
- }
- var begin = mIterator,
- end = masterData.length - 1;
- if (masterData[end].DT < date) {
- mIterator = end + 1;
- return;
- } else if (+masterData[end].DT == +date) {
- mIterator = end;
- return;
- }
- mIterator++;
- var attempts = 0;
- while (++attempts < 100) {
- switch (testIt()) {
- case 0:
- return;
- case 1:
- begin = mIterator;
- break;
- case -1:
- end = mIterator;
- break;
- }
- mIterator = Math.round((end + begin) / 2);
- }
- if (attempts >= 100) {
- console.log(
- "!!!Warning: addMemberToMasterdata() did not find insertion point."
- );
- mIterator = masterData.length - 1;
- }
- }
-
- var dateFormatter = CIQ.yyyymmddhhmmssmmm;
- /* The value for *displayDate* on quotes created below will be done by the call to ChartEngine#setMasterData */
-
- // insert/update up to masterData last bar
- while (data && mIterator < masterData.length && cIterator < data.length) {
- c = data[cIterator];
- m = masterData[mIterator];
- if (!c.DT || typeof c.DT == "undefined") c.DT = CIQ.strToDateTime(c.Date);
- else {
- if (typeof c.DT == "number") c.DT = new Date(c.DT); //in case they sent in an epoch
- if (!c.Date || c.Date.length != 17) c.Date = dateFormatter(c.DT);
- }
- if (cIterator === 0) {
- for (var s1 = 0; s1 < series.length; s1++) {
- if (!series[s1].endPoints.begin || series[s1].endPoints.begin > c.DT)
- series[s1].endPoints.begin = c.DT;
- }
- }
- if (+c.DT == +m.DT) {
- injectData(m, c);
- cIterator++;
- mIterator++;
- continue;
- }
-
- if (c.DT < m.DT) {
- masterData.splice(mIterator, 0, { DT: c.DT, Date: c.Date });
- continue;
- } else fastSeek(c.DT); // this advances the mIterator
- }
-
- // insert after master data last bar
- if (mIterator >= masterData.length) {
- while (data && cIterator < data.length) {
- c = data[cIterator];
- if (!c.DT || typeof c.DT == "undefined") c.DT = CIQ.strToDateTime(c.Date);
- else {
- if (typeof c.DT == "number") c.DT = new Date(c.DT); //in case they sent in an epoch
- if (!c.Date || c.Date.length != 17) c.Date = dateFormatter(c.DT);
- }
- m = {
- DT: c.DT,
- Date: c.Date
- };
- injectData(m, c);
- masterData.push(m);
- cIterator++;
- }
- }
- if (params.fillGaps && masterData.length) {
- var cleanupGapsParams = {
- noCleanupDates: true,
- cleanupGaps: params.fillGaps
- };
- if (fields) {
- for (var j = 0; j < fields.length; j++) {
- cleanupGapsParams.field = fields[j];
- stx.doCleanupGaps(masterData, chart, cleanupGapsParams);
- }
- } else {
- cleanupGapsParams.field = label;
- stx.doCleanupGaps(masterData, chart, cleanupGapsParams);
- }
- }
- for (var s2 = 0; s2 < series.length; s2++) {
- var endPoints = series[s2].endPoints;
- if (!endPoints.end || !label || endPoints.end <= m[label].DT) {
- endPoints.end = label ? m[label].DT : m.DT;
- var sLabel =
- label ||
- (series[s2].parameters && series[s2].parameters.field) ||
- chart.defaultPlotField;
- series[s2].lastQuote = stx.getFirstLastDataRecord(
- masterData,
- sLabel,
- true
- );
- }
- }
- stx.setMasterData(masterData, chart, { noCleanupDates: true });
- };
-
- };
-
-
- let __js_core_math_ = (_exports) => {
-
-
- var CIQ = _exports.CIQ;
-
- /* Easing cubics from
- http://gizma.com/easing/#expo1
- t = current time (t should move from zero to d)
- b = starting value
- c = change in value (b + c = ending value )
- d = duration
- */
-
- Math.easeInOutQuad = function (t, b, c, d) {
- t /= d / 2;
- if (t < 1) return (c / 2) * t * t + b;
- t--;
- return (-c / 2) * (t * (t - 2) - 1) + b;
- };
-
- Math.easeInOutCubic = function (t, b, c, d) {
- t /= d / 2;
- if (t < 1) return (c / 2) * t * t * t + b;
- t -= 2;
- return (c / 2) * (t * t * t + 2) + b;
- };
-
- Math.easeOutCubic = function (t, b, c, d) {
- t /= d;
- t--;
- return c * (t * t * t + 1) + b;
- };
-
- /**
- * Convenience function to compute xor operation.
- *
- * @param {object} a Operand.
- * @param {object} b Operand.
- * @return {boolean} true if only one of the operands is truthy.
- * @memberof CIQ
- * @since 7.3.0
- */
- CIQ.xor = function (a, b) {
- var _a = !a,
- _b = !b; // convert to boolean
- return _a !== _b;
- };
-
- /**
- * Convenience function to round a floating point number.
- *
- * This has better decimal accuracy than:
- * - number.toFixed(decimals)
- * - Math.round(number*decimals)/decimals
- * @param {number} number The number to round
- * @param {number} decimals The number of decimal places
- * @return {number} Rounded number
- * @memberof CIQ
- * @since 7.0.0
- */
- CIQ.round = function (number, decimals) {
- return Number(Math.round(number + "e" + decimals) + "e-" + decimals);
- };
-
- /**
- * Convenience function to count number of decimal places in a number
- * @param {number} n The number to check
- * @return {number} Number of decimal places
- * @memberof CIQ
- * @since
- * - 6.1.0
- * - 6.2.0 Now handles scientific notation.
- */
- CIQ.countDecimals = function (n) {
- if (typeof n !== "number" || isNaN(n)) return 0;
- if (Math.floor(n) === Number(n)) return 0;
- var strN = n.toString().split("e-");
- if (strN.length > 1)
- return CIQ.countDecimals(Number(strN[0])) + Number(strN[1]);
- if (strN[0].indexOf(".") > -1) return strN[0].split(".")[1].length;
- return 0;
- };
-
- /**
- * Convenience function to determine if a value is a valid number.
- * @param {number} n The number to check
- * @return {boolean} True if n is a real finite number. NaN, Infinity, null, undefined, etc are not considered to be a valid number.
- * @memberof CIQ
- * @since 5.2.2
- */
- CIQ.isValidNumber = function (n) {
- return isFinite(n) && +n === n;
- };
-
- /**
- * Returns the log base 10 of a value
- * @param {number} y The value
- * @return {number} log10 value
- * @memberof CIQ
- */
- CIQ.log10 = function (y) {
- return Math.log(y) / Math.LN10;
- };
-
- /**
- * Determines whether a line intersects a box. This is used within the charting engine to determine whether the cursor
- * has intersected a drawing.
- * Note this function is meant to receive bx1, by1, bx2, by2, x0, y0, x1 and y1 as pixel values and not as ticks/axis values.
- * @param {number} bx1
- * @param {number} by1
- * @param {number} bx2
- * @param {number} by2
- * @param {number} x0
- * @param {number} y0
- * @param {number} x1
- * @param {number} y1
- * @param {string} vtype - Either "segment", "ray" or "line". Anything else will default to segment.
- * @return {boolean} Returns true if the line intersects the box
- * @memberof CIQ
- * @since
- * - 4.0.0 Added `isLog` parameter.
- * - 6.0.0 Removed `isLog` parameter.
- */
- CIQ.boxIntersects = function (bx1, by1, bx2, by2, x0, y0, x1, y1, vtype) {
- if (arguments[9] !== undefined) {
- console.warn(
- "CIQ.boxIntersects() no longer supports isLog argument, please be sure arguments are passed in as pixels."
- );
- }
- var minX = Math.min(bx1, bx2);
- var maxX = Math.max(bx1, bx2);
- var minY = Math.min(by1, by2);
- var maxY = Math.max(by1, by2);
- var isRay = vtype == "ray";
-
- // Check for invalid values
- if (isNaN(x0) || isNaN(x1) || isNaN(y0) || isNaN(y1)) return false;
-
- // First see if segment/ray lies outside the box
- if (vtype != "line") {
- if (x0 < minX && x1 < minX && (!isRay || x0 > x1)) return false;
- if (x0 > maxX && x1 > maxX && (!isRay || x0 < x1)) return false;
- if (y0 < minY && y1 < minY && (!isRay || y0 > y1)) return false;
- if (y0 > maxY && y1 > maxY && (!isRay || y0 < y1)) return false;
- }
- // Now see if all box corners land on the same side of the line
- function cornerCheck(x, y) {
- return (y - y0) * (x1 - x0) - (x - x0) * (y1 - y0);
- }
- var map = {
- a: cornerCheck(bx1, by1),
- b: cornerCheck(bx1, by2),
- c: cornerCheck(bx2, by1),
- d: cornerCheck(bx2, by2)
- };
- if (map.a > 0 && map.b > 0 && map.c > 0 && map.d > 0) return false;
- if (map.a < 0 && map.b < 0 && map.c < 0 && map.d < 0) return false;
-
- return true;
- };
-
- /**
- * Determines whether two lines intersect
- * @param {number} x1
- * @param {number} x2
- * @param {number} y1
- * @param {number} y2
- * @param {number} x3
- * @param {number} x4
- * @param {number} y3
- * @param {number} y4
- * @param {string} type - Either "segment", "ray" or "line"
- * @return {boolean} Returns true if the two lines intersect
- * @memberof CIQ
- */
- CIQ.linesIntersect = function (x1, x2, y1, y2, x3, x4, y3, y4, type) {
- var denom = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1);
- var numera = (x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3);
- var numerb = (x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3);
- //var EPS = .000001;
-
- if (denom === 0) {
- if (numera === 0 && numerb === 0) return true; // coincident
- return false; // parallel
- }
-
- var mua = numera / denom;
- var mub = numerb / denom;
- if (type == "segment") {
- if (mua >= 0 && mua <= 1 && mub >= 0 && mub <= 1) return true;
- } else if (type == "line" || type == "horizontal" || type == "vertical") {
- if (mua >= 0 && mua <= 1) return true;
- } else if (type == "ray") {
- if (mua >= 0 && mua <= 1 && mub >= 0) return true;
- }
- return false;
- };
-
- /**
- * Determines the Y value at which point X intersects a line (vector)
- * @param {object} vector - Object of type {x0,x1,y0,y1}
- * @param {number} x - X value
- * @return {number} - Y intersection point
- * @memberof CIQ
- */
- CIQ.yIntersection = function (vector, x) {
- var x1 = vector.x0,
- x2 = vector.x1,
- x3 = x,
- x4 = x;
- var y1 = vector.y0,
- y2 = vector.y1,
- y3 = 0,
- y4 = 10000;
- var denom = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1);
- var numera = (x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3);
- //var numerb = (x2-x1) * (y1-y3) - (y2-y1) * (x1-x3);
- //var EPS = .000001;
-
- var mua = numera / denom;
- if (denom === 0) {
- if (numera === 0) mua = 1;
- else return null;
- }
-
- var y = y1 + mua * (y2 - y1);
- return y;
- };
-
- /**
- * Determines the X value at which point Y intersects a line (vector)
- * @param {object} vector - Object of type {x0,x1,y0,y1}
- * @param {number} y - Y value
- * @return {number} - X intersection point
- * @memberof CIQ
- */
- CIQ.xIntersection = function (vector, y) {
- var x1 = vector.x0,
- x2 = vector.x1,
- x3 = 0,
- x4 = 10000;
- var y1 = vector.y0,
- y2 = vector.y1,
- y3 = y,
- y4 = y;
- var denom = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1);
- var numera = (x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3);
- //var numerb = (x2-x1) * (y1-y3) - (y2-y1) * (x1-x3);
- //var EPS = .000001;
-
- var mua = numera / denom;
- if (denom === 0) {
- if (numera === 0) mua = 1;
- else return null;
- }
-
- var x = x1 + mua * (x2 - x1);
- return x;
- };
-
- /**
- * Get the X intersection point between two lines
- * @param {number} ax1 Initial x coordinate start point of the first box.
- * @param {number} ax2 Final x coordinate end point of the first box.
- * @param {number} ay1 Initial y coordinate start point of the first box.
- * @param {number} ay2 Final y coordinate end point of the fist box.
- * @param {number} bx1 Initial x coordinate start point of the second box.
- * @param {number} bx2 Final x coordinate end point of the second box.
- * @param {number} by1 Initial y coordinate start point of the second box.
- * @param {number} by2 Final y coordinate end point of the second box.
- * @memberof CIQ
- */
- CIQ.intersectLineLineX = function (ax1, ax2, ay1, ay2, bx1, bx2, by1, by2) {
- var ua_t = (bx2 - bx1) * (ay1 - by1) - (by2 - by1) * (ax1 - bx1);
- var u_b = (by2 - by1) * (ax2 - ax1) - (bx2 - bx1) * (ay2 - ay1);
-
- var ua = ua_t / u_b;
-
- return ax1 + ua * (ax2 - ax1);
- };
-
- /**
- * Get the Y intersection point between two lines
- * @param {number} ax1 Initial x coordinate start point of the first box.
- * @param {number} ax2 Final x coordinate end point of the first box.
- * @param {number} ay1 Initial y coordinate start point of the first box.
- * @param {number} ay2 Final y coordinate end point of the fist box.
- * @param {number} bx1 Initial x coordinate start point of the second box.
- * @param {number} bx2 Final x coordinate end point of the second box.
- * @param {number} by1 Initial y coordinate start point of the second box.
- * @param {number} by2 Final y coordinate end point of the second box.
- * @memberof CIQ
- */
- CIQ.intersectLineLineY = function (ax1, ax2, ay1, ay2, bx1, bx2, by1, by2) {
- var ua_t = (bx2 - bx1) * (ay1 - by1) - (by2 - by1) * (ax1 - bx1);
- var u_b = (by2 - by1) * (ax2 - ax1) - (bx2 - bx1) * (ay2 - ay1);
-
- var ua = ua_t / u_b;
-
- return ay1 + ua * (ay2 - ay1);
- };
-
- };
-
-
- let __js_core_object_ = (_exports) => {
-
-
- var CIQ = _exports.CIQ;
-
- /**
- * Deletes the map entries for which the right hand side is the object in question.
- * @param {object} map JavaScript map object
- * @param {object} object The actual object to be deleted from the map
- * @return {boolean} Returns true if any object actually deleted
- * @memberof CIQ
- */
- CIQ.deleteRHS = function (map, object) {
- var deletedOne = false;
- for (var i in map) {
- if (map[i] == object) {
- delete map[i];
- deletedOne = true;
- }
- }
- return deletedOne;
- };
-
- /**
- * Deletes (removes) nulls or undefined fields (names) from an object. This is useful when marshalling (saving) an object where you don't wish
- * null or undefined values to show up in the marshalled object (such as when converting to JSON)
- * @param {object} obj The object to scrub
- * @param {boolean} [removeNulls] Whether or not to remove null values
- * @memberof CIQ
- */
- CIQ.scrub = function (obj, removeNulls) {
- for (var i in obj) {
- if (typeof obj[i] == "undefined") delete obj[i];
- if (removeNulls && obj[i] === null) delete obj[i];
- }
- };
-
- /**
- * This method changes the target object's contents to match the contents of the source object. This is functionally equivalent
- * to `target=source` except that it preserves the existence of the target object. This is vitally important if there are data bindings
- * to the target object otherwise those data bindings would remain attached to a phantom object! The logic here is orchestrated so that you
- * will receive update, add and delete notifications for each field that changes.
- * @param {object} target The target object
- * @param {object} source The source object
- * @memberof CIQ
- * @since 2015-11-1
- */
- CIQ.dataBindSafeAssignment = function (target, source) {
- /*for(var prop in source) {
- target[prop]=source[prop];
- }*/
- CIQ.extend(target, source);
- for (var prop in target) {
- if (typeof source[prop] == "undefined") {
- target[prop] = undefined;
- }
- }
- };
-
- /**
- * Clones an object. This function creates a deep (recursive) clone of an object. The object can be a primitive or an object or an array.
- * Note that cloning objects that reference DOM nodes can result in stack overflows. Use with caution.
- * @param {object} from The source object
- * @param {object} [to] Optional existing object of same type. Can improve performance when objects are reusable.
- * @return {object} A deep clone of the "from" object
- * @memberof CIQ
- */
- CIQ.clone = function (from, to) {
- if (from === null || typeof from != "object") return from;
- var c = from.constructor;
- if (c == Date || c == RegExp || c == String || c == Number || c == Boolean)
- return new c(from.valueOf());
- if (c == Function)
- return function () {
- return from.apply(this, arguments);
- };
-
- if (!to) {
- try {
- to = new c();
- } catch (e0) {
- to = Object.create(Object.getPrototypeOf(from));
- }
- }
-
- for (var n in from) {
- to[n] = to[n] !== from[n] ? CIQ.clone(from[n], null) : to[n];
- }
-
- return to;
- };
-
- /**
- * Non recursive clone. This will only clone the top layer and is safe to use when objects contain DOM nodes.
- * @param {object} from - Object to be cloned
- * @return {object} A shallow clone of the "from" object
- * @memberof CIQ
- */
- CIQ.shallowClone = function (from) {
- if (!from) return from;
- var to;
- if (from.constructor == Array) {
- to = new Array(from.length);
- for (var i = 0; i < from.length; i++) {
- to[i] = from[i];
- }
- return to;
- }
- to = {};
- for (var field in from) {
- to[field] = from[field];
- }
- return to;
- };
-
- /**
- * Accepts a default parameters object and sets the field values for the target *only if they are missing*.
- * Note that a value of null will not be overridden. Only undefined (missing) values will be overridden.
- * @param {object} target The object needing potential default values
- * @param {object} defaults Default values
- * @return {object} Returns the modified target object
- * @since 3.0.0
- * @example
- * var target={"color":"red"};
- * var defaults={"color":"blue", "shape":"triangle"};
- * CIQ.ensureDefaults(target, defaults);
- * >> target==={"color":"red", "shape":"triangle"};
- * @memberof CIQ
- */
- CIQ.ensureDefaults = function (target, defaults) {
- for (var field in defaults) {
- if (typeof target[field] == "undefined") target[field] = defaults[field];
- }
- return target;
- };
-
- /**
- * Copies the contents of one object into another.
- * This is useful if there are pointers to the target object and you want to "replace" it with another object while preserving the pointer.
- * @param {object} target The object being pointed to
- * @param {object} source The object containing the values you want pointed at
- * @return {object} Returns the modified target object
- * @since 7.1.0
- * @example
- * var target={"color":"red", "pattern":"solid"};
- * var source={"color":"blue", "shape":"triangle"};
- * CIQ.transferObject(target, source);
- * >> target==={"color":"blue", "shape":"triangle"};
- * >> target!==source;
- * @memberof CIQ
- */
- CIQ.transferObject = function (target, source) {
- var field;
- for (field in target) {
- if (target.hasOwnProperty(field)) delete target[field];
- }
- for (field in source) {
- if (source.hasOwnProperty(field)) target[field] = source[field];
- }
- return target;
- };
-
- /**
- * Returns true if the objects are an exact match
- * @param {object} a First object
- * @param {object} b Second object
- * @param {object} [exclude] Exclude these fields
- * @return {boolean} True if they are an exact match
- * @memberof CIQ
- */
- CIQ.equals = function (a, b, exclude) {
- if (!a && b) return false;
- if (a && !b) return false;
- if (typeof a !== typeof b) return false;
- for (var field in a) {
- if (exclude && exclude[field]) continue;
- if (typeof a[field] === "object") {
- var result = CIQ.equals(a[field], b[field]);
- if (!result) return false;
- continue;
- }
- if (b[field] != a[field]) return false;
- }
- return true;
- };
-
- /**
- * Returns true if an object has no members
- * @param {object} o A JavaScript object
- * @return {boolean} True if there are no members in the object
- * @memberof CIQ
- */
- CIQ.isEmpty = function (o) {
- for (var p in o) {
- if (o.hasOwnProperty(p)) {
- return false;
- }
- }
- return true;
- };
-
- /**
- * Convenience function returns the first property in an object. Note that while this works in all known browsers
- * the EMCA spec does not guarantee that the order of members in an object remain static. This method should therefore
- * be avoided. When ordering is important use an Array!
- * @param {object} o A JavaSCript object
- * @return {object} The first element in the object or null if it is empty
- * @memberof CIQ
- */
- CIQ.first = function (o) {
- for (var p in o) {
- return p;
- }
- return null;
- };
-
- /**
- * Convenience function for returning the last property in an object. Note that while this works in all known browsers
- * the EMCA spec does not guarantee that the order of members in an object remain static. This method should therefore
- * be avoiding. When ordering is important use an Array!
- * @param {object} o A JavaScript object
- * @return {object} The final member of the object or null if the object is empty
- * @memberof CIQ
- */
- CIQ.last = function (o) {
- var l = null;
- for (var p in o) {
- l = p;
- }
- return l;
- };
-
- /**
- * Returns the number of members in an object
- * @param {object} o A valid JavaScript object
- * @return {number} The number of members in the object
- * @memberof CIQ
- */
- CIQ.objLength = function (o) {
- if (!o) return 0;
- var i = 0;
- for (var p in o) {
- i++;
- }
- return i;
- };
-
- /**
- * Given a dot notation string, we want to navigate to the location
- * in a base object, creating the path along the way
- * @param {object} base Base object.
- * @param {string} extension String in dot notation
- * @return {object} A tuple containing obj and member
- * @memberof CIQ
- * @since 2015-11-1
- * @example
- * var tuple=CIQ.deriveFromObjectChain(stx.layout, "pandf.box");
- * tuple.obj===stx.layout.pandf
- * tuble.member==="box"
- * tuple.obj[tuple.member]=3; // stx.layout.pandf.box=3
- */
- CIQ.deriveFromObjectChain = function (base, extension) {
- // Which way is faster?
- //if(!(new RegExp(extension)).test(".")){
- if (extension.indexOf(".") == -1) {
- return { obj: base, member: extension };
- }
- var objectString = extension.split(".");
- for (var i = 0; i < objectString.length - 1; i++) {
- var objStr = objectString[i];
- if (!base[objStr] && base[objStr] !== 0) base[objStr] = {};
- base = base[objStr];
- }
- return { obj: base, member: objectString[i] };
- };
-
- /**
- * Create arrow notation strings (field-->property) of a given field and an array of properties
- * Used to create a set of object properties in string format for later use by CIQ.existsInObjectChain
- * Its main use is to pass field names into {@link CIQ.ChartEngine#determineMinMax}.
- * @param {string} field Base object.
- * @param {array} properties Array of strings representing properties
- * @return {array} Array of object properties expressed in arrow notation (field-->property)
- * @memberof CIQ
- * @since 5.1.0
- * @example
- * var fields=CIQ.createObjectChainNames("ABC.D",["High","Low"]);
- * fields===["ABC.D-->High","ABC.D-->Low"]
- */
- CIQ.createObjectChainNames = function (field, properties) {
- var ret = [];
- for (var p = 0; p < properties.length; p++) {
- ret.push(field + "-->" + properties[p]);
- }
- return ret;
- };
-
- /**
- * Given an arrow notation string (a-->b-->c), we want to navigate to the location
- * in a base object, to see if it exists
- * @param {object} base Base object.
- * @param {string} extension String in arrow notation
- * @return {object} A tuple containing obj and member; a null will be returned if path does not exist
- * @memberof CIQ
- * @since 5.1.0
- * @example
- * var tuple=CIQ.existsInObjectChain(stx.dataSegment[0], "ABC.D-->High");
- * tuple.obj===stx.dataSegment[0]["ABC.D"]
- * tuple.member==="High"
- * tuple.obj[tuple.member]=28.7; // stx.dataSegment[0]["ABC.D"].High=28.7
- */
- CIQ.existsInObjectChain = function (base, extension) {
- // Which way is faster?
- //if(!(new RegExp(extension)).test(".")){
- if (extension.indexOf("-->") == -1) {
- if (!base[extension] && base[extension] !== 0) return null;
- return { obj: base, member: extension };
- }
- var objectString = extension.split("-->");
- var objStr;
- for (var i = 0; i < objectString.length - 1; i++) {
- objStr = objectString[i];
- if (!base[objStr] && base[objStr] !== 0) return null;
- base = base[objStr];
- }
- objStr = objectString[i];
- if (!base[objStr] && base[objStr] !== 0) return null;
- return { obj: base, member: objStr };
- };
-
- /**
- * Replacement for isPrototypeOf and instanceof so that both types of inheritance can be checked
- * @param {object} child The object instance to check
- * @param {object} parent Prototype
- * @return {boolean} True if the object is derived from the parent
- * @memberof CIQ
- * @since 07/01/2015
- */
- CIQ.derivedFrom = function (child, parent) {
- if (parent.isPrototypeOf(child)) return true;
- if (child instanceof parent) return true;
- return false;
- };
-
- /**
- * This method will iterate through the object and replace all of the fields
- * using the mapping object. This would generally be used to compress an object
- * for serialization. so that for instance "lineWidth" becomes "lw". This method
- * is called recursively.
- * @param {object} obj Object to compress
- * @param {object} mapping Object containing name value pairs. Each name will be replaced with its corresponding value in the object.
- * @return {object} The newly compressed object
- * @memberof CIQ
- */
- CIQ.replaceFields = function (obj, mapping) {
- if (!obj) return obj;
- var newObj = {};
- for (var field in obj) {
- var value = obj[field];
- var replaced = mapping[field];
- if (!replaced) replaced = field;
- if (value && typeof value == "object") {
- if (value.constructor == Array) {
- var arr = (newObj[replaced] = new Array(value.length));
- for (var i = 0; i < arr.length; i++) {
- var val = value[i];
- if (typeof val == "object") {
- arr[i] = CIQ.replaceFields(val, mapping);
- } else {
- arr[i] = val;
- }
- }
- } else {
- newObj[replaced] = CIQ.replaceFields(value, mapping);
- }
- } else {
- newObj[replaced] = value;
- }
- }
- return newObj;
- };
-
- /**
- * Returns an object copy with any null values removed
- * @param {object} obj Object to remove nulls
- * @return {object} Object with nulls removed
- * @memberof CIQ
- */
- CIQ.removeNullValues = function (obj) {
- var n = CIQ.clone(obj);
- for (var f in n) {
- if (!n[f]) delete n[f];
- }
- return n;
- };
-
- /**
- * This method reverses the fields and values in an object
- * @param {object} obj Object to reverse
- * @return {object} The reversed object
- * @memberof CIQ
- * @example reverseObject({ one: "a", two: "b" }) // returns { a: "one", b: "two" }
- */
- CIQ.reverseObject = function (obj) {
- var newObj = {};
- for (var field in obj) {
- newObj[obj[field]] = field;
- }
- return newObj;
- };
-
- /**
- * Accesses a property, method, or array in a namespace.
- *
- * Approximates optional chaining, checking whether the object at the end of `namespace` +
- * `path` exists before returning it.
- *
- * @param {object} namespace Namespace to access.
- * @param {string} path String in dot notation representing extension of the namespace to a
- * desired property, method, or array.
- * @param {*} [defaultValue] The value returned if the requested expression does not exist.
- * If the requested expression is a function, set `defaultValue` to a function (usually
- * `function(){}`) that can be run with any required arguments. If the requested
- * expression is an array, set `defaultValue` to a default array, usually `[]`.
- * @return {*} The expression sought by combining the namespace and path. If the expression
- * does not exist, returns `defaultValue` (if provided), otherwise returns `undefined`.
- *
- * @memberof CIQ
- * @since 8.0.0
- *
- * @example
- * // Accesses CIQ.Studies.studyLibrary.rsi if safe to do so (if exists).
- * CIQ.getFromNS(CIQ.Studies, "studyLibrary.rsi");
- * // or
- * CIQ.getFromNS(CIQ, "Studies.studyLibrary.rsi");
- *
- * @example
- * // Accesses Math.Matrix.ScalarOperations.dotProduct(mA, mB) if safe to do so (if exists).
- * // Returns 12 if Math.Matrix.ScalarOperations.dotProduct does not exist.
- * CIQ.getFromNS(Math, "Matrix.ScalarOperations.dotProduct", (a,b)=>a*b)(3, 4);
- */
- CIQ.getFromNS = (namespace, path, defaultValue) => {
- if (namespace) {
- var base = namespace,
- objectString = path.split(".");
- for (var i = 0; i < objectString.length; i++) {
- var objStr = objectString[i];
- if (typeof base[objStr] === "undefined") break;
- base = base[objStr];
- }
- if (i === objectString.length) return base;
- }
- return undefined || defaultValue;
- };
-
- /**
- * Curried {@link CIQ.getFromNS} expecting a function to be returned if `obj` + `path` is not
- * found.
- *
- * @param {object} obj Namespace to access.
- * @param {string} path String in dot notation representing extension of the namespace to
- * the desired function.
- * @param {*} [defaultValue] The value returned if the requested function does not exist.
- * @return {function} The function sought by combining the namespace and path. If the
- * function does not exist, returns `function(){return defaultValue;}`.
- *
- * @memberof CIQ
- * @since 8.0.0
- *
- * @example
- * // Invokes Math.Matrix.ScalarOperations.dotProduct with arguments (mA, mB) if safe to do so (if exists).
- * // Assigns NaN to the result if Math.Matrix.ScalarOperations.dotProduct does not exist.
- * let result=getFnFromNS(Math, "Matrix.ScalarOperations.dotProduct", NaN)(mA, mB);
- */
- CIQ.getFnFromNS = (obj, path, defaultValue) => {
- return CIQ.getFromNS(obj, path, function () {
- return defaultValue;
- }); // use `function` to allow `new (CIQ.getFromNS())(...) syntax
- };
-
- /**
- * Curried {@link CIQ.getFromNS} expecting the namespace to be {@link CIQ}.
- *
- * @param {string} path String in dot notation representing extension of the {@link CIQ}
- * namespace to a desired property, method, or array.
- * @param {*} [defaultValue] The value returned if the requested expression does not exist.
- * If the requested expression is a function, set `defaultValue` to a function (usually
- * `function(){}`) that can be run with any required arguments. If the requested
- * expression is an array, set `defaultValue` to a default array, usually `[]`.
- * @return {*} The expression sought by combining the {@link CIQ} namespace and the path. If
- * the expression does not exist, returns `defaultValue` (if provided), otherwise returns
- * undefined.
- *
- * @memberof CIQ
- * @since 8.0.0
- *
- * @example
- * // Accesses CIQ.Studies.studyLibrary.rsi if safe to do so (if exists).
- * CIQ.get("Studies.studyLibrary.rsi");
- * // Returns null if CIQ.Studies.studyLibrary.rsi does not exist.
- * CIQ.get("Studies.studyLibrary.rsi", null);
- */
- CIQ.get = (path, defaultValue) => {
- return CIQ.getFromNS(CIQ, path, defaultValue);
- };
-
- /**
- * Curried {@link CIQ.getFromNS} expecting the namespace to be {@link CIQ} and a function to be
- * returned.
- *
- * @param {string} path String in dot notation representing extension of the {@link CIQ}
- * namespace to the desired function.
- * @param {*} [defaultValue] The value returned if the requested function does not exist.
- * @return {function} The function sought by combining the {@link CIQ} namespace and the path.
- * If the function does not exist, returns `function(){return defaultValue;}`.
- *
- * @memberof CIQ
- * @since 8.0.0
- *
- * @example
- * // Returns the function if safe to do so (if exists).
- * // Assigns "error" to the result if CIQ.Studies.removeStudy does not exist.
- * getFn("Studies.removeStudy", "error");
- */
- CIQ.getFn = (path, defaultValue) => {
- return CIQ.getFromNS(CIQ, path, function () {
- return defaultValue;
- }); // use `function` to allow `new (CIQ.getFromNS())(...) syntax
- };
-
- };
-
-
- let __js_core_plotter_ = (_exports) => {
-
-
- var CIQ = _exports.CIQ;
-
- /**
- * The Plotter is a device for managing complex drawing operations on the canvas. The HTML 5 canvas performs better when drawing
- * operations of the same color are batched (reducing the number of calls to the GPU). The plotter allows a developer to store those
- * operations in a normal control flow, and then have the Plotter deliver the primitives to the canvas. The plotter can also be used
- * as a caching mechanism for performing the same operations repeatedly. The y-axis of the chart uses this mechanism to boost performance.
- * @constructor
- * @name CIQ.Plotter
- */
- CIQ.Plotter = function () {
- this.seriesArray = [];
- this.seriesMap = {};
- };
-
- CIQ.extend(
- CIQ.Plotter.prototype,
- {
- /**
- * Define a series to plot. A series is a specific color and referenced by name
- * @param {string} name Name of series
- * @param {boolean} strokeOrFill If true then a stroke operation, otherwise a fill operation
- * @param {string} color A valid canvas color
- * @param {number} [opacity=1] A valid opacity from 0-1
- * @param {number} [width=1] A valid lineWidth from 1
- * @param {string} [pattern=solid] A valid pattern (solid, dotted, dashed)
- * @memberof CIQ.Plotter#
- * @since 4.0.0 added parameter pattern
- */
- Series: function (name, strokeOrFill, color, opacity, width, pattern) {
- this.name = name;
- this.strokeOrFill = strokeOrFill;
- this.color = color;
- this.moves = [];
- this.text = [];
- if (!opacity || opacity > 1 || opacity < 0) opacity = 1;
- this.opacity = opacity;
- if (!width || width > 25 || width < 1) width = 1;
- this.width = width;
- this.pattern = CIQ.borderPatternToArray(width, pattern);
- },
- /**
- * Create a series. This supports either a text color or CIQ.ChartEngine.Style object
- * @see CIQ.Plotter.Series
- * @memberof CIQ.Plotter#
- * @param {string} name Name of series
- * @param {boolean} strokeOrFill If true then a stroke operation, otherwise a fill operation
- * @param {object|string} colorOrStyle Color or object containing color, opacity, width, borderTopStyle (solid, dotted, dashed)
- * @param {number} [opacity] A valid opacity from 0-1
- * @param {number} [width=1] A valid lineWidth from 1
- */
- newSeries: function (name, strokeOrFill, colorOrStyle, opacity, width) {
- var series;
- if (colorOrStyle.constructor == String)
- series = new this.Series(
- name,
- strokeOrFill,
- colorOrStyle,
- opacity,
- width
- );
- else
- series = new this.Series(
- name,
- strokeOrFill,
- colorOrStyle.color,
- colorOrStyle.opacity,
- width >= 0 ? width : CIQ.stripPX(colorOrStyle.width),
- colorOrStyle.borderTopStyle
- );
- this.seriesArray.push(series);
- this.seriesMap[name] = series;
- },
- /**
- * Clear out any moves or text stored in the plotter for series "name"
- * @memberof CIQ.Plotter#
- * @param {string} name Name of series to reset. If omitted, will reset all series in plotter.
- * @since 3.0.0
- */
- reset: function (name) {
- for (var s in this.seriesMap) {
- if (name && name != s) continue;
- var series = this.seriesMap[s];
- if (series) {
- series.moves = [];
- series.text = [];
- }
- }
- },
- /**
- * @param {string} name Name of series
- * @param {number} x
- * @param {number} y
- * @memberof CIQ.Plotter#
- */
- moveTo: function (name, x, y) {
- var series = this.seriesMap[name];
- series.moves.push({ action: "moveTo", x: x, y: y });
- },
- /**
- * @param {string} name Name of series
- * @param {number} x
- * @param {number} y
- * @memberof CIQ.Plotter#
- */
- lineTo: function (name, x, y) {
- var series = this.seriesMap[name],
- pattern = series.pattern;
- series.moves.push({ action: "lineTo", x: x, y: y, pattern: pattern });
- },
- /**
- * @param {string} name Name of series
- * @param {number} x
- * @param {number} y
- * @param {string} pattern A valid pattern (solid, dotted, dashed)
- * @memberof CIQ.Plotter#
- */
- dashedLineTo: function (name, x, y, pattern) {
- var series = this.seriesMap[name];
- series.moves.push({ action: "lineTo", x: x, y: y, pattern: pattern });
- },
- /**
- * @param {string} name Name of series
- * @param {number} cx0 Control point x-coord
- * @param {number} cy0 Control point y-coord
- * @param {number} x
- * @param {number} y
- * @memberof CIQ.Plotter#
- */
- quadraticCurveTo: function (name, cx0, cy0, x, y) {
- var series = this.seriesMap[name],
- pattern = series.pattern;
- series.moves.push({
- action: "quadraticCurveTo",
- x0: cx0,
- y0: cy0,
- x: x,
- y: y,
- pattern: pattern
- });
- },
- /**
- * @param {string} name Name of series
- * @param {number} cx0 First control point x-coord
- * @param {number} cy0 First control point y-coord
- * @param {number} cx1 Second control point x-coord
- * @param {number} cy1 Second control point x-coord
- * @param {number} x
- * @param {number} y
- * @memberof CIQ.Plotter#
- * @since 4.0.0
- */
- bezierCurveTo: function (name, cx0, cy0, cx1, cy1, x, y) {
- var series = this.seriesMap[name],
- pattern = series.pattern;
- series.moves.push({
- action: "bezierCurveTo",
- x0: cx0,
- y0: cy0,
- x1: cx1,
- y1: cy1,
- x: x,
- y: y,
- pattern: pattern
- });
- },
- /**
- * Add text to be rendered with the drawing. Primarily used when the Plotter is used for caching since there is no
- * performance benefit from batching text operations to the GPU. If specifying a bounding box, textBaseline="middle" is assumed
- * @param {string} name Name of series
- * @param {string} text The raw text to render
- * @param {number} x X position on canvas for text
- * @param {number} y Y position on canvas for text
- * @param {string} [backgroundColor] Color to use on the box underneath the text
- * @param {number} [width] Width of bounding box
- * @param {number} [height] Height of bounding box
- * @memberof CIQ.Plotter#
- */
- addText: function (name, text, x, y, backgroundColor, width, height) {
- var series = this.seriesMap[name];
- series.text.push({ text: text, x: x, y: y, bg: backgroundColor });
- },
- /**
- * Renders the text objects. This is done after drawing primitives for each series.
- * @private
- * @memberof CIQ.Plotter#
- */
- drawText: function (context, series) {
- for (var i = 0; i < series.text.length; i++) {
- var textObj = series.text[i];
- if (textObj.bg) {
- var w = textObj.width
- ? textObj.width
- : context.measureText(textObj.text).width;
- var h = textObj.height ? textObj.height : 12;
- var prev = context.fillStyle;
- context.fillStyle = textObj.bg;
- if (context.textAlign == "right") {
- context.fillRect(textObj.x, textObj.y - h / 2, -w, -h);
- } else {
- context.fillRect(textObj.x, textObj.y - h / 2, w, h);
- }
- context.fillStyle = prev;
- }
- context.fillText(textObj.text, textObj.x, textObj.y);
- }
- },
- /**
- * Render the plotter. All of the stored operations are sent to the canvas. This operation stores and restores
- * global canvas parameters such as fillStyle, strokeStyle and globalAlpha.
- * @param {object} context A valid HTML canvas context
- * @param {string} [name] Optionally render only a specific series. If null or not provided then all series will be rendered.
- * @memberof CIQ.Plotter#
- */
- draw: function (context, name) {
- var prevWidth = context.lineWidth;
- var prevFillStyle = context.fillStyle;
- var prevStrokeStyle = context.strokeStyle;
- var prevGlobalAlpha = context.globalAlpha;
- for (var i = 0; i < this.seriesArray.length; i++) {
- var series = this.seriesArray[i];
- if (name && series.name != name) continue;
- context.beginPath();
- context.lineWidth = series.width;
- context.globalAlpha = series.opacity;
- context.fillStyle = series.color;
- context.strokeStyle = series.color;
- context.save();
- for (var j = 0; j < series.moves.length; j++) {
- var move = series.moves[j];
- if (move.pattern) {
- context.setLineDash(move.pattern);
- context.lineDashOffset = 0;
- } else context.setLineDash([]);
- if (move.action == "quadraticCurveTo") {
- context[move.action](move.x0, move.y0, move.x, move.y);
- } else if (move.action == "bezierCurveTo") {
- context[move.action](
- move.x0,
- move.y0,
- move.x1,
- move.y1,
- move.x,
- move.y
- );
- } else {
- context[move.action](move.x, move.y);
- }
- }
- if (series.strokeOrFill == "fill") {
- context.fill();
- } else {
- context.stroke();
- }
- context.closePath();
- context.restore();
- this.drawText(context, series);
- context.lineWidth = 1;
- }
- context.lineWidth = prevWidth;
- context.fillStyle = prevFillStyle;
- context.strokeStyle = prevStrokeStyle;
- context.globalAlpha = prevGlobalAlpha;
- }
- },
- true
- );
-
- };
-
-
- let __js_core_renderer_ = (_exports) => {
-
-
- var CIQ = _exports.CIQ;
-
- /**
- * Base class for Renderers.
- *
- * A renderer is used to draw a complex visualization based on one or more "series" of data.
- * Renderers only need to be attached to a chart once. You can change symbols and continue using the same renderer.
- * The series associated with a renderer may change at any time, but the linked renderer itself remains the vehicle for display them.
- *
- * Series are associated with renderers by calling attachSeries().
- * More typically though, this is done automatically when {@link CIQ.ChartEngine#addSeries} is used.
- * The parameters for addSeries() are passed both to the renderer's constructor and also to attachSeries().
- *
- * To manually create a renderer use {@link CIQ.ChartEngine#setSeriesRenderer}
- *
- * @name CIQ.Renderer
- * @constructor
- */
- CIQ.Renderer = function () {};
-
- /**
- * Factory for renderer.
- *
- * Will request a renderer from each renderer subclass until it is given one.
- * @param {string} chartType Chart type name (usually from layout.chartType)
- * @param {object} [params] Parameters to pass to the renderer constructor
- * @memberof CIQ.Renderer
- * @since 5.1.0
- * @private
- */
- CIQ.Renderer.produce = function (chartType, params) {
- var renderer = null;
- if (chartType) {
- for (var r in CIQ.Renderer) {
- var rendererType = CIQ.Renderer[r];
- // Note: chartType has often been a combination of attributes connected with an underscore,
- // e.g. colored_bar, baseline_mountain. So we split this legacy name to get the attributes.
- if (rendererType.requestNew)
- renderer = rendererType.requestNew(chartType.split("_"), params);
- if (renderer) return renderer;
- }
- }
- params.type = "line";
- return new CIQ.Renderer.Lines({ params: params });
- };
-
- CIQ.Renderer.colorFunctions = {};
- /**
- * Registers a colorFunction for use with a renderer.
- *
- * It is necessary to register a color function if you want the function to be tied back to an imported renderer.
- * @param {string} name The name of the registered function
- * @param {function} funct The function to register
- * @memberof CIQ.Renderer
- */
- CIQ.Renderer.registerColorFunction = function (name, funct) {
- CIQ.Renderer.colorFunctions[name] = funct;
- };
-
- /**
- * Unregisters a colorFunction for use with a renderer.
- *
- * @param {string} name The name of the registered function
- * @memberof CIQ.Renderer
- */
- CIQ.Renderer.unregisterColorFunction = function (name) {
- delete CIQ.Renderer.colorFunctions[name];
- };
-
- /**
- * If your renderer manages a yAxis then the necessary adjustments to its properties should be made here.
- *
- * @memberof CIQ.Renderer
- * @since 5.2.0
- */
- CIQ.Renderer.prototype.adjustYAxis = function () {};
-
- /**
- * Perform drawing operations here.
- * @memberof CIQ.Renderer
- */
- CIQ.Renderer.prototype.draw = function () {};
-
- /**
- * Draws one series from the renderer.
- *
- * Called by {@link CIQ.ChartEngine.AdvancedInjectable#drawSeries}
- * @param {CIQ.ChartEngine.Chart} chart The chart object to draw the renderers upon
- * @param {object} [parameters] Parameters used to draw the series, depends on the renderer type
- * @param {string} [parameters.panel] Name of panel to draw the series upon
- * @memberof CIQ.Renderer
- * @since 5.1.0
- */
- CIQ.Renderer.prototype.drawIndividualSeries = function (chart, parameters) {};
-
- /**
- * Default constructor for a renderer. Override this if desired.
- * @param {object} config Configuration for the renderer.
- * @param {function} [config.callback] Callback function to perform activity post-drawing, for example, creating a legend. It will be called with an object containing the list of instruments and corresponding colors.
- * @param {string} [config.id] Handle to access the rendering in the future. If not provided, one will be generated.
- * @param {object} [config.params] Parameters to control the renderer itself
- * @param {string} [config.params.name="Data"] Name of the renderer. This is used when displaying error message on screen
- * @param {string} [config.params.panel="chart"] The name of the panel to put the rendering on.
- * @param {boolean} [config.params.overChart=true] If set to false, will draw the rendering behind the main chart rather than over it. By default rendering will be as overlay on the main chart.
- * @param {boolean} [config.params.yAxis] Y-axis object to use for the series.
- * @param {number} [config.params.opacity=1] Opacity of the rendering as a whole. Can be overridden by an opacity set for a series. Valid values are 0.0-1.0.
Not applicable on [Lines]{@link CIQ.Renderer.Lines} with a `type` of `mountain`; use an "RGBA" color instead.
- * @param {object} [config.params.binding] Allows the use of the study output colors within the renderer. See an example in the [Using Renderers to Display Study Output](tutorial-Using%20and%20Customizing%20Studies%20-%20Creating%20New%20Studies.html#Using_Renderers) section of the Studies tutorial.
- * @memberof CIQ.Renderer
- * @since 5.2.0 `config.params.binding` parameter added.
- * @example
- * // add multiple series and attach to a custom y-axis on the left.
- * // See this example working here : https://jsfiddle.net/chartiq/b6pkzrad
- *
- * // note how the addSeries callback is used to ensure the data is present before the series is displayed
- *
- * //create the custom axis
- * var axis=new CIQ.ChartEngine.YAxis();
- * axis.position="left";
- * axis.textStyle="#FFBE00";
- * axis.decimalPlaces=0; // no decimal places on the axis labels
- * axis.maxDecimalPlaces=0; // no decimal places on the last price pointer
- *
- * //create the renderer
- * var renderer=stxx.setSeriesRenderer(new CIQ.Renderer.Lines({params:{name:"my-renderer", type:"mountain", yAxis:axis}}));
- *
- * // create your series and attach them to the chart when the data is loaded.
- * stxx.addSeries("NOK", {display:"NOK",width:4},function(){
- * renderer.attachSeries("NOK", "#FFBE00").ready();
- * });
- *
- * stxx.addSeries("SNE", {display:"Sony",width:4},function(){
- * renderer.attachSeries("SNE", "#FF9300").ready();
- * });
- */
- CIQ.Renderer.prototype.construct = function (config) {
- if (!config) config = {};
- var params = config.params ? config.params : {};
- if (!params.name) params.name = CIQ.uniqueID();
- if (!params.heightPercentage) params.heightPercentage = 0.7;
- if (!params.opacity) params.opacity = 1;
- if (params.highlightable !== false) params.highlightable = true;
- if (!params.panel) params.panel = "chart";
- if (params.yAxis) {
- params.yAxis = new CIQ.ChartEngine.YAxis(params.yAxis);
- if (!params.yAxis.name) params.yAxis.name = params.name;
- }
- this.cb = config.callback;
- this.params = params;
- this.seriesParams = [];
- this.caches = {};
- this.colors = {};
- };
-
- /**
- * Attach a series to the renderer.
- *
- * This assumes that the series data *is already in the dataSet* and simply connects it to the renderer with the specified parameters.
- * See {@link CIQ.ChartEngine#addSeries} for details on how to create a series.
- *
- * Any parameters defined when attaching a series, such as colors, will supersede any defined when a series was created. This allows you to attach the same series to multiple renderers, each rendering displaying the same series data in a different color, for example.
- *
- * @param {string} id The name of the series.
- * @param {object} parameters Settings to control color and opacity of each series in the group. See {@link CIQ.ChartEngine#addSeries} for implementation examples.
- * @param {string} [parameters.field] The name of the field. Name of the field in the dataSet to use for the series. If omitted, defaults to id
- * @param {string} [parameters.fill_color_up] Color to use to fill the part when the Close is higher than the previous (or 'transparent' to not display)
- * @param {string} [parameters.border_color_up] Color to use to draw the border when the Close is higher than the previous (or 'transparent' to not display)
- * @param {number} [parameters.opacity_up=.4] Opacity to use to fill the part when the Close is higher than the previous (0.0-1.0)
- * @param {string} [parameters.border_color_even] Color to use to draw the border when the Close is equal to the previous (or 'transparent' to not display)
- * @param {string} [parameters.fill_color_down] Color to use to fill the part when the Close is lower than the previous (or 'transparent' to not display)
- * @param {string} [parameters.border_color_down] Color to use to draw the border when the Close is lower than the previous (or 'transparent' to not display)
- * @param {number} [parameters.opacity_down=.4] Opacity to use to fill the part when the Close is lower than the previous (0.0-1.0)
- * @param {string} [parameters.color] Color to use to fill the series in the absence of specific up/down color.
- * @param {string} [parameters.border_color] Color to use to draw the border in the series in the absence of specific up/down color.
- * @param {string} [parameters.fillStyle] Color to use to fill the mountain chart.
- * @param {string} [parameters.baseColor] Color to use at the bottom of the mountain chart, will create a gradient with bgColor
- * @param {string} [parameters.bgColor] Color to use at the top of the mountain chart, will create a gradient if baseColor is specified. Otherwise, will fill the mountain solid with this color unless fillStyle is specified
- * @param {boolean} [parameters.permanent] Whether the attached series can be removed by the user (lines and bars only). By default the series will not be permanent. This flag (including the default) will supersede the permanent flag of the actual series. As such, a series will not be permanent unless you set this flag to 'true', even if the series being attached was flaged set as permanent when defined. This gives the renderer most control over the rendering process.
- * @return {CIQ.Renderer} Returns a copy of this for chaining
- * @since 5.1.0 Added `fillStyle`, `baseColor`, and `bgColor` parameters.
- * @memberof CIQ.Renderer
- * @example
- * // add multiple series and attach to a custom y-axis on the left.
- * // See this example working here : https://jsfiddle.net/chartiq/b6pkzrad
- *
- * // note how the addSeries callback is used to ensure the data is present before the series is displayed
- *
- * //create the custom axis
- * var axis=new CIQ.ChartEngine.YAxis();
- * axis.position="left";
- * axis.textStyle="#FFBE00";
- * axis.decimalPlaces=0; // no decimal places on the axis labels
- * axis.maxDecimalPlaces=0; // no decimal places on the last price pointer
- *
- * //create the renderer
- * var renderer=stxx.setSeriesRenderer(new CIQ.Renderer.Lines({params:{name:"my-renderer", type:"mountain", yAxis:axis}}));
- *
- * // create your series and attach them to the chart when the data is loaded.
- * stxx.addSeries("NOK", {display:"NOK",width:4},function(){
- * renderer.attachSeries("NOK", "#FFBE00").ready();
- * });
- *
- * stxx.addSeries("SNE", {display:"Sony",width:4},function(){
- * renderer.attachSeries("SNE", "#FF9300").ready();
- * });
- */
- CIQ.Renderer.prototype.attachSeries = function (id, parameters) {
- var stx = this.stx;
- if (!stx) return this;
- var series = stx.chart.series[id];
- if (!series) series = { parameters: {} };
- var rParams = this.params,
- sParams = series.parameters;
- var sp = {
- id: id,
- chartType: rParams.type,
- display: sParams.display,
- border_color_up: rParams.defaultBorders ? "auto" : null,
- fill_color_up: sParams.color,
- opacity_up: rParams.opacity,
- border_color_down: rParams.defaultBorders ? "auto" : null,
- fill_color_down: sParams.color,
- opacity_down: rParams.opacity,
- color: sParams.color,
- symbol: sParams.symbol,
- symbolObject: CIQ.clone(sParams.symbolObject)
- };
- if (typeof parameters == "string") {
- sp.color = sp.fill_color_down = sp.fill_color_up = parameters;
- } else if (typeof parameters == "object") {
- for (var p in parameters) sp[p] = parameters[p];
- var c = sp.color,
- bc = sp.border_color;
- if (c) {
- if (!sp.fill_color_up) sp.fill_color_up = c;
- if (!sp.fill_color_down) sp.fill_color_down = c;
- if (!sp.fill_color_even) sp.fill_color_even = c;
- }
- if (bc) {
- if (!sp.border_color_up) sp.border_color_up = bc;
- if (!sp.border_color_down) sp.border_color_down = bc;
- if (!sp.border_color_even) sp.border_color_even = bc;
- }
- }
- if (sp.symbol && sp.field != sp.symbol) {
- sp.subField = sp.field;
- sp.field = sp.symbol;
- }
- //if(!sp.symbol && !sp.field && !this.highLowBars) sp.field="Close";
- if (!sp.id) sp.id = CIQ.uniqueID();
-
- var i = 0;
- for (; i < this.seriesParams.length; ++i) {
- if (this.seriesParams[i].id === sp.id) {
- this.removeSeries(sp.id, true);
- break;
- }
- }
- this.seriesParams.splice(i, 0, sp);
-
- if (sp.fill_color_up != sp.fill_color_down) {
- this.colors[id + " up"] = {
- color: sp.fill_color_up,
- opacity: sp.opacity_up,
- display: sp.display ? sp.display + " up" : id + " up"
- };
- this.colors[id + " dn"] = {
- color: sp.fill_color_down,
- opacity: sp.opacity_down,
- display: sp.display ? sp.display + " down" : id + " down"
- };
- } else {
- this.colors[id] = {
- color: sp.fill_color_up,
- opacity: sp.opacity_up,
- display: sp.display ? sp.display : id
- };
- }
-
- var panelName = rParams.panel;
- if (!stx.panels[panelName]) {
- var yAxis = rParams.yAxis;
- if (!yAxis) {
- yAxis = new CIQ.ChartEngine.YAxis();
- yAxis.needsInitialPadding = true;
- }
- yAxis.name = panelName;
- stx.createPanel(panelName, panelName, null, null, yAxis);
- } else {
- if (rParams.yAxis) {
- rParams.yAxis = stx.addYAxis(stx.panels[panelName], rParams.yAxis);
- rParams.yAxis.needsInitialPadding = true;
- sParams.yAxis = rParams.yAxis;
- stx.resizeChart();
- } else if (sp.yAxis) {
- rParams.yAxis = sp.yAxis;
- }
- }
-
- return this;
- };
-
- /**
- * Removes a series from the renderer.
- *
- * The yAxis and actual series data will also be removed if no longer used by any other renderers.
- * When the last series is removed from the renderer, the chart it is attached to will remove the renderer.
- * Will [turn off comparison mode]{@link CIQ.ChartEngine#setComparison} if there are no more comparisons on the chart if {@link CIQ.ChartEngine.Chart#forcePercentComparison} is true.
- * @param {string} id The field name of the series.
- * @param {boolean} [preserveSeries=false] Set to `true` to keep the series data in the CIQ.ChartEngine objects, otherwise it iwll be deleted if no
- * @return {CIQ.Renderer} A copy of this for chaining
- * @memberof CIQ.Renderer
- * @since
- * - 2015-07-01 'preserveSeries' is now available.
- * - 3.0.0 Series is now removed even if series parameter `permanent` is set to true. The permanent parameter only prevents right click user interaction and not programmatically requested removals.
- * - 4.0.0 Series data is now totally removed from masterData if no longer used by any other renderers.
- * - 6.2.0 No longer force 'percent'/'linear', when adding/removing comparison series, respectively, unless {@link CIQ.ChartEngine.Chart#forcePercentComparison} is true. This allows for backwards compatibility with previous UI modules.
- */
- CIQ.Renderer.prototype.removeSeries = function (id, preserveSeries) {
- var spliceIndex = null,
- comparing = false;
- var stx = this.stx,
- chart = stx.chart;
- for (var r in chart.seriesRenderers) {
- var renderer = chart.seriesRenderers[r];
- for (var sp = 0; sp < renderer.seriesParams.length; sp++) {
- var seriesParams = renderer.seriesParams[sp];
- if (seriesParams.id == id && this === renderer) spliceIndex = sp;
- else if (
- seriesParams.isComparison &&
- seriesParams.panel == chart.panel.name &&
- (!seriesParams.yAxis || seriesParams.yAxis == chart.yAxis)
- )
- comparing = true;
- }
- }
- if (spliceIndex !== null) {
- if (
- chart.forcePercentComparison &&
- !comparing &&
- this.seriesParams[spliceIndex].isComparison &&
- stx.layout.chartScale != "linear"
- ) {
- stx.setChartScale();
- }
- this.seriesParams.splice(spliceIndex, 1);
- }
-
- delete this.colors[id + " up"];
- delete this.colors[id + " dn"];
- delete this.colors[id];
-
- if (!preserveSeries) {
- //if(!stx.chart.series[id] || !stx.chart.series[id].parameters.permanent){
- var seriesInUse;
- for (var plot in chart.seriesRenderers) {
- var myPlot = chart.seriesRenderers[plot];
- for (var s = 0; s < myPlot.seriesParams.length; s++) {
- if (myPlot.seriesParams[s].id == id) {
- seriesInUse = true;
- break;
- }
- seriesInUse = false;
- }
- if (seriesInUse) break;
- }
- if (seriesInUse === false || spliceIndex !== null) {
- stx.deleteSeries(id, chart);
- }
- //}
- }
- stx.deleteYAxisIfUnused(stx.panels[this.params.panel], this.params.yAxis);
- stx.resizeChart();
- stx.layout.symbols = stx.getSymbols({
- "include-parameters": true,
- "exclude-studies": true
- });
- stx.changeOccurred("layout");
- return this;
- };
-
- /**
- * Modifies the renderer parameters.
- *
- * Use this function to trigger side effects from modifying parameters instead of manually
- * updating the parameters.
- *
- * @param {object} parameters Specifies the renderer parameters to be updated.
- *
- * @memberof CIQ.Renderer
- * @private
- * @since 8.2.0
- */
- CIQ.Renderer.prototype.modifyRenderer = function (parameters) {
- const original = this.params;
- let { stx } = this;
-
- for (let param in parameters) {
- const value = parameters[param];
- switch (param) {
- case "baseline":
- if (value) {
- if (typeof value === "object") {
- this.params.baseline = CIQ.ensureDefaults(
- value,
- CIQ.ChartEngine.Chart.prototype.baseline
- );
- }
- stx.registerBaselineToHelper(this);
- } else {
- stx.removeBaselineFromHelper(this);
- }
- break;
-
- case "type":
- this.params.type = value;
- break;
- case "style":
- this.params.style = value;
- break;
- default:
- break;
- }
- }
- };
-
- /**
- * Returns an array of all renderers that depend on a given renderer.
- *
- * A dependent renderer is one that has `params.dependentOf` set to another renderer's name.
- *
- * @param {CIQ.ChartEngine} stx A chart object.
- * @return {array} Array of dependent renderers.
- * @memberof CIQ.Renderer
- * @since 7.3.0
- */
- CIQ.Renderer.prototype.getDependents = function (stx) {
- var dependents = [];
- for (var r in stx.chart.seriesRenderers) {
- var renderer = stx.chart.seriesRenderers[r];
- if (renderer.params.dependentOf == this.params.name)
- dependents.push(renderer);
- }
- return dependents;
- };
-
- /**
- * Returns whether the renderer can be dragged to another axis or panel.
- *
- * @param {CIQ.ChartEngine} stx A chart object.
- * @return {boolean} true, if not allowed to drag.
- * @memberof CIQ.Renderer
- * @since 7.3.0
- */
- CIQ.Renderer.prototype.undraggable = function (stx) {
- if (this == stx.mainSeriesRenderer) return true;
- return this.params.dependentOf == stx.mainSeriesRenderer.params.name;
- };
-
- /**
- * Removes all series from the renderer and the yAxis from the panel if it is not being used by any current renderers.
- *
- * @param {boolean} [eraseData=false] Set to true to erase the actual series data in the CIQ.ChartEngine otherwise it will be retained
- * @return {CIQ.Renderer} A copy of this for chaining
- * @memberof CIQ.Renderer
- */
- CIQ.Renderer.prototype.removeAllSeries = function (eraseData) {
- if (eraseData || this === this.stx.mainSeriesRenderer) {
- var arr = [];
- // Compile a list of all of the fields
- for (var sp = 0; sp < this.seriesParams.length; sp++) {
- arr.push(this.seriesParams[sp].id);
- }
- for (var i = 0; i < arr.length; i++) {
- this.removeSeries(arr[i]);
- }
- }
- this.seriesParams = [];
- this.colors = {};
- this.stx.deleteYAxisIfUnused(
- this.stx.panels[this.params.panel],
- this.params.yAxis
- );
- this.stx.resizeChart();
-
- return this;
- };
-
- /**
- * Returns the y-axis used by the renderer
- * @param {CIQ.ChartEngine} stx chart engine object
- * @return {CIQ.ChartEngine.YAxis} Y axis
- * @memberof CIQ.Renderer
- * @since 7.1.0
- */
- CIQ.Renderer.prototype.getYAxis = function (stx) {
- var yAxis;
- if (this.params) {
- if (this.params.yAxis) yAxis = this.params.yAxis;
- else {
- var panel = stx.panels[this.params.panel];
- if (!panel) return false;
- yAxis = panel.yAxis;
- }
- } else yAxis = stx.chart.panel.yAxis;
- return yAxis;
- };
-
- /**
- * Call this to immediately render the visualization, at the end of a chain of commands.
- * @return {CIQ.Renderer} A copy of this for chaining
- * @memberof CIQ.Renderer
- */
- CIQ.Renderer.prototype.ready = function () {
- this.stx.createDataSet();
- this.stx.draw();
- return this;
- };
-
- /**
- * Creates a lines renderer.
- *
- * This renderer draws lines of various color, thickness, and pattern on a chart.
- *
- * The `Lines` renderer is used to create the following chart types (including colored versions):
- * line, mountain, baseline, wave, and step chart.
- *
- * **Note:** By default, the renderer displays lines as underlays. As such, they appear below any
- * other studies or drawings.
- *
- * See {@link CIQ.Renderer#construct} for parameters required by all renderers.
- *
- * @param {object} config Configuration object for the renderer.
- * @param {object} [config.params] Parameters to control the renderer itself.
- * @param {number} [config.params.width] Width of the rendered line.
- * @param {string} [config.params.type="line"] Type of rendering; "line", "mountain", or
- * ["wave"]{@link CIQ.ChartEngine#drawWaveChart}.
- * @param {boolean} [config.params.useChartLegend=false] Set to true to use the built-in canvas
- * legend renderer. See {@link CIQ.ChartEngine.Chart#legendRenderer};
- * @param {boolean} [config.params.highlightable=true] Set to false to prevent selection of series
- * via hover.
- * @param {string} [config.params.style] Style name to use in lieu of defaults for the type.
- * @param {boolean} [config.params.step] Specifies a step chart.
- * @param {boolean|CIQ.ChartEngine.Chart#baseline} [config.params.baseline] Specifies a baseline
- * chart. If a baseline object is set, then the renderer uses those properties instead of the
- * chart's baseline when rendering. When true, the renderer falls back to the chart's baseline
- * properties for rendering.
- * @param {boolean} [config.params.colored] Specifies the use of a color function (see
- * {@link CIQ.Renderer.registerColorFunction}) to determine the color of the segment.
- * @param {boolean} [config.params.vertex] Specifies drawing a dot on every vertex.
- * @param {boolean} [config.params.vertex_color] Specifies a color for the vertices. If omitted,
- * uses the default color (see {@link CIQ.ChartEngine#getDefaultColor}).
- * @param {string} [config.params.colorFunction] Override string (or function) used to determine
- * color of bar. May be an actual function or a string name of the registered function (see
- * {@link CIQ.Renderer.registerColorFunction}).
- *
- * Common valid parameters for use by {@link CIQ.Renderer#attachSeries} (see also
- * {@link CIQ.ChartEngine#plotLine}):
- * - `color` — Specify the color for the line by name or in RGBA or hexadecimal format.
- * - `pattern` — Specify the pattern as an array. For instance, [5, 5] would be five pixels
- * and then five empty pixels.
- * - `width` — Specify the width of the line.
- * - `baseColor` — Specify the color of the base of a mountain.
- * - `fillStyle` — Specify the color to fill a mountain (other than `color`).
- *
- * @constructor
- * @name CIQ.Renderer.Lines
- * @since
- * - 4.0.0 New `config.params.useChartLegend` added.
- * - 5.1.0 Removed subtype parameter, this will be determined internally from
- * `config.params.step=true`.
- * - 5.1.0 Added `highlightable`, `overChart`, `step`, `baseline`, `vertex`, `style`, `colored`,
- * and `colorFunction` parameters.
- * - 8.1.0 Added {@link CIQ.ChartEngine.Chart#baseline} type to `baseline` parameter. The new type
- * contains a `defaultLevel` property which can be set to the desired baseline value. See
- * example below.
- *
- * @example
and is normally not directly accessed.
- *
- * See {@link CIQ.Renderer#construct} for parameters required by all renderers
- * @param {object} config Config for renderer
- * @param {object} [config.params] Parameters to control the renderer itself
- * @param {string} [config.params.type] Type of rendering "bar", "candle". Not needed if `params.histogram` is set)
- * @param {boolean} [config.params.useChartLegend=false] Set to true to use the built in canvas legend renderer. See {@link CIQ.ChartEngine.Chart#legendRenderer};
- * @param {string} [config.params.style] Style name to use in lieu of defaults for the type
- * @param {boolean} [config.params.colored] For bar or hlc, specifies using a condition or colorFunction to determine color
- * @param {boolean} [config.params.hollow] Specifies candles should be hollow candles
- * @param {boolean} [config.params.volume] Specifies candles should be volume candles
- * @param {boolean} [config.params.histogram] Specifies histogram chart (if set, `params.type` is not required). These are basic histograms that allow just one bar per tick; not to be confused with stackable histograms which require the more advanced {@link CIQ.Renderer.Histogram}
- * @param {boolean} [config.params.hlc] Specifies bar chart, with just hlc data; no open tick
- * @param {boolean} [config.params.gradient=true] Specifies histogram bars are to be drawn with a gradient; set to false to draw with solid colors
- * @param {string} [config.params.colorBasis="close"] For bar/hlc charts, will compute color based on whether current close is higher or lower than previous close. Set to "open" to compute this off the open rather than yesterday's close.
- * @param {function} [config.params.colorFunction] Oerride function (or string) used to determine color of bar. May be an actual function or a string name of the registered function (see {@link CIQ.Renderer.registerColorFunction})
- * @constructor
- * @name CIQ.Renderer.OHLC
- * @since 5.1.0
- * @example
- // Colored hlc chart
- var renderer=stxx.setSeriesRenderer(new CIQ.Renderer.OHLC({params:{name:"bars", type:"bar", hlc:true, colored:true}}));
- *
- */
-
- CIQ.Renderer.OHLC = function (config) {
- this.construct(config);
- var params = this.params;
- if (!params.type) params.type = "candle";
- this.highLowBars = this.barsHaveWidth = this.standaloneBars = true;
- if (params.histogram) {
- params.type = "candle";
- this.highLowBars = false;
- params.volume = params.hollow = false;
- }
- if (params.type == "bar")
- params.volume = params.hollow = params.histogram = false;
- if (params.type == "candle") params.hlc = params.colored = false;
- if (params.volume) params.hollow = true;
- };
- CIQ.inheritsFrom(CIQ.Renderer.OHLC, CIQ.Renderer, false);
-
- /**
- * Returns a new OHLC renderer if the featureList calls for it
- * FeatureList should contain whatever features requested; valid features:
- * bar, hlc, candle, colored, histogram, hollow, volume
- * Anything else is an invalid feature and will cause function to return null
- *
- * **Note:** If you are using the base package then the only valid features are: candle and histogram.
- *
- * Called by {@link CIQ.Renderer.produce} to create a renderer for the main series
- * @param {array} featureList List of rendering terms requested by the user, parsed from the chartType
- * @param {object} [params] Parameters used for the series to be created, used to create the renderer
- * @return {CIQ.Renderer.OHLC} A new instance of the OHLC renderer, if the featureList matches
- * @memberof CIQ.Renderer.OHLC
- * @since 5.1.0
- */
- CIQ.Renderer.OHLC.requestNew = function (featureList, params) {
- var type = null,
- histogram = params.histogram;
- for (var pt = 0; pt < featureList.length; pt++) {
- var pType = featureList[pt];
- switch (pType) {
- case "candle":
- type = pType;
- break;
- case "histogram":
- histogram = true;
- type = "candle";
- break;
- default:
- return null; // invalid chartType for this renderer
- }
- }
- if (type === null) return null;
-
- return new CIQ.Renderer.OHLC({
- params: CIQ.extend(params, { type: type, histogram: histogram })
- });
- };
-
- /**
- * Returns array of chartParts for configuring rendering.
- *
- * @since 7.4.0
- * @private
- */
- CIQ.Renderer.OHLC.getChartParts = function (style, colorUseOpen) {
- var CANDLEUP = 8; // today's close greater than today's open
- var CANDLEDOWN = 16; // today's close less than today's open
- var CANDLEEVEN = 32; // today's close equal to today's open
- return [
- {type:"histogram", drawType:"histogram", style:"stx_histogram_up", condition:CANDLEUP, fill:"fill_color_up", border:"border_color_up", useColorInMap:true, useBorderStyleProp:true},
- {type:"histogram", drawType:"histogram", style:"stx_histogram_down", condition:CANDLEDOWN, fill:"fill_color_down", border:"border_color_down", useColorInMap:true, useBorderStyleProp:true},
- {type:"histogram", drawType:"histogram", style:"stx_histogram_even", condition:CANDLEEVEN, fill:"fill_color_even", border:"border_color_even", skipIfPass:true, useColorInMap:true, useBorderStyleProp:true},
- {type:"candle", drawType:"shadow", style:"stx_candle_shadow", border:"border_color_up"},
- {type:"candle", drawType:"shadow", style:"stx_candle_shadow_up", condition:CANDLEUP, border:"border_color_up"},
- {type:"candle", drawType:"shadow", style:"stx_candle_shadow_down", condition:CANDLEDOWN, border:"border_color_down"},
- {type:"candle", drawType:"shadow", style:"stx_candle_shadow_even", condition:CANDLEEVEN, border:"border_color_even", skipIfPass:true},
- {type:"candle", drawType:"candle", style:"stx_candle_up", condition:CANDLEUP, fill:"fill_color_up", border:"border_color_up", useColorInMap:true, useBorderStyleProp:true},
- {type:"candle", drawType:"candle", style:"stx_candle_down", condition:CANDLEDOWN, fill:"fill_color_down", border:"border_color_down", useColorInMap:true, useBorderStyleProp:true},
- ]; // prettier-ignore
- };
-
- CIQ.Renderer.OHLC.prototype.draw = function () {
- var stx = this.stx,
- panel = this.stx.panels[this.params.panel],
- chart = panel.chart;
- var seriesMap = {};
- var s,
- seriesParams = this.seriesParams;
- for (s = 0; s < seriesParams.length; s++) {
- var sParam = seriesParams[s];
-
- var defaultParams = {};
- if (chart.series[sParam.id]) {
- // make sure the series is still there.
- defaultParams = CIQ.clone(chart.series[sParam.id].parameters);
- }
- seriesMap[sParam.id] = {
- parameters: CIQ.extend(CIQ.extend(defaultParams, this.params), sParam)
- //yValueCache: this.caches[sParam.id]
- };
- if (
- this == stx.mainSeriesRenderer &&
- chart.customChart &&
- chart.customChart.colorFunction
- ) {
- seriesMap[sParam.id].parameters.colorFunction =
- chart.customChart.colorFunction;
- }
- }
- stx.drawSeries(chart, seriesMap, this.params.yAxis, this);
- for (s in seriesMap) {
- if (seriesMap[s].yValueCache) this.caches[s] = seriesMap[s].yValueCache;
- }
- };
-
- CIQ.Renderer.OHLC.prototype.getColor = function (
- stx,
- panel,
- style,
- isBorder,
- isGradient,
- overrideColor
- ) {
- var color = overrideColor || style.color;
- var yAxis = this.params.yAxis || panel.yAxis;
- if (isBorder) {
- color =
- overrideColor || style.borderLeftColor || style["border-left-color"];
- if (!color) return null;
- }
- if (!isGradient) return color;
- var top = stx.pixelFromTransformedValue(yAxis.highValue, panel);
- if (isNaN(top)) top = 0; // 32 bit IE doesn't like large numbers
- var backgroundColor = style.backgroundColor;
- if (color && !CIQ.isTransparent(color)) {
- var gradient = stx.chart.context.createLinearGradient(
- 0,
- top,
- 0,
- 2 * yAxis[yAxis.flipped ? "top" : "bottom"] - top
- );
- gradient.addColorStop(0, color);
- gradient.addColorStop(1, backgroundColor);
- return gradient;
- }
- return backgroundColor;
- };
-
- CIQ.Renderer.OHLC.prototype.drawIndividualSeries = function (
- chart,
- parameters
- ) {
- if (parameters.invalid) return;
- var stx = this.stx,
- context = chart.context;
- var colorFunction = parameters.colorFunction,
- panel = stx.panels[parameters.panel] || chart.panel;
- if (typeof colorFunction == "string") {
- colorFunction = CIQ.Renderer.colorFunctions[colorFunction];
- if (!colorFunction) return;
- }
- var noBorders =
- stx.layout.candleWidth - chart.tmpWidth <= 2 && chart.tmpWidth <= 3;
- var CLOSEUP = 1; // today's close greater than yesterday's close
- var CLOSEDOWN = 2; // today's close less than yesterday's close
- var CLOSEEVEN = 4; // today's close the same as yesterday's close
- var CANDLEUP = 8; // today's close greater than today's open
- var CANDLEDOWN = 16; // today's close less than today's open
- var CANDLEEVEN = 32; // today's close equal to today's open
- if (!chart.state.chartType) chart.state.chartType = {};
- var pass = (chart.state.chartType.pass = {});
- var colorUseOpen = stx.colorByCandleDirection;
- if (parameters.colorBasis) colorUseOpen = parameters.colorBasis == "open";
- var isHistogram = parameters.histogram,
- type = parameters.type,
- hollow = parameters.hollow;
- var noWicks = stx.noWicksOnCandles[type];
- stx.startClip(panel.name);
- var colors = null,
- rc = { colors: [], cache: [] },
- caches = [];
- if (colorFunction) {
- var drawingParams = {
- isHistogram: isHistogram,
- field: parameters.field,
- yAxis: parameters.yAxis,
- isVolume: parameters.volume,
- highlight: parameters.highlight
- };
- if (!isHistogram && type == "bar") {
- drawingParams.type = parameters.hlc ? "hlc" : "bar";
- rc = stx.drawBarChart(
- panel,
- "stx_bar_chart",
- colorFunction,
- drawingParams
- );
- } else {
- if (type == "candle" && !noWicks)
- stx.drawShadows(panel, colorFunction, drawingParams);
- rc = stx.drawCandles(panel, colorFunction, drawingParams); //all bars
- drawingParams.isOutline = true;
- if (hollow || !noBorders)
- stx.drawCandles(panel, colorFunction, drawingParams); //all bar borders, if candlewidth is too small then don't draw the borders
- }
- } else {
- var isGradient = isHistogram && parameters.gradient !== false;
- var chartParts = CIQ.Renderer.OHLC.getChartParts(
- parameters.style,
- colorUseOpen
- );
- for (var i = 0; i < chartParts.length; i++) {
- var chartPart = chartParts[i];
- if (chartPart.skipIfPass && !pass.even) continue;
- else if (isHistogram) {
- if (chartPart.type != "histogram") continue;
- } else if (type == "bar") {
- if (chartPart.type != "bar") continue;
- else if (parameters.colored && !chartPart.condition) continue;
- else if (!parameters.colored && chartPart.condition) continue;
- } else if (hollow) {
- if (chartPart.type != "hollow") continue;
- else if (chartPart.drawType == "shadow" && noWicks) continue;
- } else if (type == "candle") {
- if (chartPart.type != "candle") continue;
- else if (chartPart.drawType == "shadow") {
- if (noWicks) continue;
- var coloredShadowUp =
- parameters.border_color_up ||
- stx.getCanvasColor("stx_candle_shadow_up");
- var coloredShadowDown =
- parameters.border_color_down ||
- stx.getCanvasColor("stx_candle_shadow_down");
- var coloredShadowEven =
- parameters.border_color_even ||
- stx.getCanvasColor("stx_candle_shadow_even");
- if (
- !CIQ.colorsEqual(coloredShadowUp, coloredShadowDown) ||
- !CIQ.colorsEqual(coloredShadowUp, coloredShadowEven) ||
- !CIQ.colorsEqual(coloredShadowUp, stx.defaultColor)
- ) {
- if (!chartPart.condition) continue;
- } else if (chartPart.condition) continue;
- }
- } else continue;
-
- var styleArray = stx.canvasStyle(chartPart.style);
- var legendColor = this.getColor(
- stx,
- panel,
- styleArray,
- false,
- false,
- parameters[chartPart.fill]
- );
- var fillColor = this.getColor(
- stx,
- panel,
- styleArray,
- false,
- isGradient,
- parameters[chartPart.fill]
- );
- var borderColor = this.getColor(
- stx,
- panel,
- styleArray,
- chartPart.useBorderStyleProp && !noBorders,
- isGradient,
- parameters[chartPart.border]
- );
- if (chartPart.drawType == "candle") {
- if (chartPart.type == "hollow") {
- // Solid candles get no border unless the border color is different than the fill color
- if (
- !CIQ.isTransparent(fillColor) &&
- CIQ.colorsEqual(borderColor, fillColor)
- )
- borderColor = chartPart.useColorInMap ? "transparent" : fillColor;
- if (!chartPart.useColorInMap) fillColor = stx.containerColor;
- } else if (chartPart.type == "candle") {
- // Check to see if the candles are too small for borders
- if (noBorders) {
- if (CIQ.isTransparent(fillColor)) fillColor = borderColor;
- // transparent candle, draw it with the border color
- else borderColor = fillColor; // non-transparent candle, set the border to the fill color
- }
- }
- }
- context.globalAlpha = parameters.opacity;
- caches.push(
- stx.drawBarTypeChartInner({
- fillColor: fillColor,
- borderColor: borderColor,
- condition: chartPart.condition,
- style: chartPart.style,
- type: type == "bar" && parameters.hlc ? "hlc" : chartPart.drawType,
- panel: panel,
- field: parameters.field,
- yAxis: parameters.yAxis,
- volume: parameters.volume && parameters.hollow,
- highlight: parameters.highlight
- })
- );
- if (!colors) colors = {};
- if (chartPart.useColorInMap) colors[legendColor] = 1;
- }
- }
- stx.endClip();
- for (var c in colors) {
- if (!parameters.hollow || !CIQ.equals(c, stx.containerColor)) {
- rc.colors.push(c);
- }
- }
- for (c = 0; c < caches.length; c++) {
- for (var x = 0; x < caches[c].cache.length; x++) {
- var v = caches[c].cache[x];
- if (v || v === 0) rc.cache[x] = v;
- }
- }
- return rc;
- };
-
- /**
- * Creates a Candles renderer, a derivation of the OHLC renderer.
- *
- * Note: by default the renderer will display candles as underlays. As such, they will appear below any other studies or drawings.
- *
- * The Candles renderer is used to create the following drawing types: candle, hollow candle, volume candle
- *
- * See {@link CIQ.Renderer#construct} for parameters required by all renderers
- * @param {object} config Config for renderer
- * @param {object} [config.params] Parameters to control the renderer itself
- * @param {boolean} [config.params.useChartLegend=false] Set to true to use the built in canvas legend renderer. See {@link CIQ.ChartEngine.Chart#legendRenderer};
- * @param {string} [config.params.style] Style name to use in lieu of defaults for the type
- * @param {boolean} [config.params.hollow] Specifies candles should be hollow candles
- * @param {boolean} [config.params.volume] Specifies candles should be volume candles
- * @param {function} [config.params.colorFunction] Override function (or string) used to determine color of candle. May be an actual function or a string name of the registered function (see {@link CIQ.Renderer.registerColorFunction})
- *
- * Common valid parameters for use by attachSeries.:
- * `fill_color_up` - Color to use for up candles.
- * `fill_color_down` - Color to use for down candles.
- * `fill_color_even` - Color to use for even candles.
- * `border_color_up` - Color to use for the border of up candles.
- * `border_color_down` - Color to use for the order of down candles.
- * `border_color_even` - Color to use for the order of even candles.
- *
- * @constructor
- * @name CIQ.Renderer.Candles
- * @since 5.1.1
- * @example
- // Hollow candle chart
- var renderer=stxx.setSeriesRenderer(new CIQ.Renderer.Candles({params:{name:"candles", hollow:true}}));
- *
- */
- CIQ.Renderer.Candles = function (config) {
- this.construct(config);
- var params = this.params;
- params.type = "candle";
- this.highLowBars = this.barsHaveWidth = this.standaloneBars = true;
- params.hlc = params.colored = params.histogram = false;
- if (params.volume) params.hollow = true;
- };
- CIQ.inheritsFrom(CIQ.Renderer.Candles, CIQ.Renderer.OHLC, false);
-
- /**
- * Creates a SimpleHistogram renderer, a derivation of the Candles renderer.
- *
- * Note: by default the renderer will display histogram as underlays. As such, they will appear below any other studies or drawings.
- *
- * The SimpleHistogram renderer is used to create a histogram with the top of each bar representing the value of the field.
- * It is a much simpler form of histogram than that produced by the Histogram renderer (advanced package).
- *
- * See {@link CIQ.Renderer#construct} for parameters required by all renderers
- * @param {object} config Config for renderer
- * @param {object} [config.params] Parameters to control the renderer itself
- * @param {boolean} [config.params.useChartLegend=false] Set to true to use the built in canvas legend renderer. See {@link CIQ.ChartEngine.Chart#legendRenderer};
- * @param {string} [config.params.style] Style name to use in lieu of defaults for the type
- * @param {boolean} [config.params.gradient=true] Specifies histogram bars are to be drawn with a gradient; set to false to draw with solid colors
- * @param {function} [config.params.colorFunction] Override function (or string) used to determine color of bar. May be an actual function or a string name of the registered function (see {@link CIQ.Renderer.registerColorFunction})
- *
- * Valid parameters for use by attachSeries.:
- * `fill_color_up` - Color to use for up histogram bars.
- * `fill_color_down` - Color to use for down histogram bars.
- * `fill_color_even` - Color to use for even histogram bars.
- * `border_color_up` - Color to use for the border of up histogram bars.
- * `border_color_down` - Color to use for the order of down histogram bars.
- * `border_color_even` - Color to use for the order of even histogram bars.
- *
- * @constructor
- * @name CIQ.Renderer.SimpleHistogram
- * @since 5.1.1
- * @example
- // SimpleHistogram under the main chart plot
- var renderer=stxx.setSeriesRenderer(new CIQ.Renderer.SimpleHistogram({params:{name:"histogram", overChart:false}}));
- *
- */
-
- CIQ.Renderer.SimpleHistogram = function (config) {
- this.construct(config);
- var params = this.params;
- params.type = "candle";
- params.histogram = true;
- this.barsHaveWidth = this.standaloneBars = true;
- this.highLowBars = false;
- params.hlc = params.colored = params.hollow = params.volume = false;
- };
- CIQ.inheritsFrom(CIQ.Renderer.SimpleHistogram, CIQ.Renderer.Candles, false);
-
- };
-
-
- let __js_core_string_ = (_exports) => {
-
-
- //-------------------------------------------------------------------------------------------
- // Be sure your webserver is set to deliver UTF-8 charset
- // For apache add "AddDefaultCharset UTF-8" to httpd.conf
- // otherwise use \u unicode escapes for non-ascii characters
- //-------------------------------------------------------------------------------------------
-
- var CIQ = _exports.CIQ;
-
- /**
- * Capitalizes the first letter of a string.
- *
- * @param {string} string String to be capitalized.
- * @return {string} Capitalized version of the string.
- * @memberof CIQ
- * @since 7.4.0 Replaces {@link String.prototype.capitalize}.
- */
- CIQ.capitalize = function (string) {
- if (!string) return "";
- return string.charAt(0).toUpperCase() + string.slice(1);
- };
-
- CIQ.camelCaseRegExp = /-([a-z])/g;
- /**
- * Converts from hyphenated to camel case. Used primarily for converting css style names (which are hyphenated) to property values (which are camel case)
- * @param {string} name Hyphenated style name
- * @return {string} Camel case style name
- * @memberof CIQ
- */
- CIQ.makeCamelCase = function (name) {
- return name.replace(CIQ.camelCaseRegExp, function (g) {
- return g[1].toUpperCase();
- });
- };
-
- /**
- * Convenience function for generating a unique ID. Defaults to a short, pseudo unique ID based on the current time.
- * Radix 36 is used resulting in a compact string consisting only of letters and numerals. While not guaranteed to be
- * unique, this function has a high probability of uniqueness when it is triggered by human activity even in a large
- * user base. If called with `true` as the first argument it will instead return an RFC4122 version 4 compliant UUID.
- * @param {boolean} generateUUID If true will return a UUID.
- * @return {string} Either a RFC4122 version 4 compliant UUID or a unique string consisting of letters and numerals
- * @memberof CIQ
- */
- CIQ.uniqueID = function (generateUUID) {
- if (generateUUID) {
- // See http://stackoverflow.com/questions/105034/create-guid-uuid-in-javascript
- var d = new Date().getTime();
- if (
- typeof window !== "undefined" &&
- window.performance &&
- typeof window.performance.now === "function"
- ) {
- d += window.performance.now(); //use high-precision timer if available
- }
- var uuid = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(
- /[xy]/g,
- function (c) {
- var r = (d + Math.random() * 16) % 16 | 0;
- d = Math.floor(d / 16);
- return (c == "x" ? r : (r & 0x3) | 0x8).toString(16);
- }
- );
- return uuid;
- }
- var epoch = new Date();
- var id = epoch.getTime().toString(36);
- id += Math.floor(Math.random() * Math.pow(36, 2)).toString(36);
- return id.toUpperCase();
- };
-
- };
-
-
- let __js_core_typedefs_ = (_exports) => {
- /**
- * OHLC Quote. This is the data format that the {@link CIQ.ChartEngine} recognizes.
- * All quotes must at least have a DT property that is a JavaScript Date in order to be valid, every other value is nullable.
- * Quotes can contain as many properties as you would like, allowing the ChartEngine to plot any value.
- *
- * @typedef {object} CIQ.ChartEngine~OHLCQuote
- * @prop {number} Open The opening price of the quote.
- * @prop {number} High The highest price of the quote.
- * @prop {number} Low The lowest price of the quote.
- * @prop {number} Close The closing price of the quote.
- * @prop {number} Volume The number of shares traded.
- * @prop {!Date} DT The date and time of the quote.
- */
-
- /**
- * CIQ.Drawing interface placeholder to be augmented in *standard.js* with properties.
- *
- * @tsinterface {object} CIQ~Drawing
- */
-
- /**
- * CIQ.ChartEngine.RangeParameters interface placeholder to be augmented in *standard.js* with properties.
- *
- * @tsinterface {object} CIQ.ChartEngine~RangeParameters
- */
-
- /**
- * CIQ.ChartEngine.SpanParameters interface placeholder to be augmented in *standard.js* with properties.
- *
- * @tsinterface {object} CIQ.ChartEngine~SpanParameters
- */
-
- /**
- * CIQ.ChartEngine.currentVectorParameters interface placeholder to be augmented in *standard.js* with properties.
- *
- * @tsinterface {object} CIQ.ChartEngine~currentVectorParameters
- */
-
- /**
- * CIQ.Studies.StudyDescriptor interface placeholder to be augmented in *standard.js* with properties.
- *
- * @tsinterface {object} CIQ.Studies~StudyDescriptor
- */
-
- };
-
-
- let __js_core_xhr_ = (_exports) => {
-
-
- var CIQ = _exports.CIQ;
-
- /**
- * Returns the host portion of a url
- * @param {string} url The url, such as document.location.href
- * @return {string} The host portion, including port, if the url is a valid URI
- * @memberof CIQ
- */
- CIQ.getHostName = function (url) {
- try {
- return url.match(/:\/\/(.[^/]+)/)[1];
- } catch (e) {
- return "";
- }
- };
-
- /**
- * A parsed query string object
- * Does not support using multi-value keys (i.e. "a=1&a=2")
- * @param {string} [query] Query string. If not provided then the browser location's query string will be used
- * @return {object} An object containing the parsed values of the query string
- * @memberof CIQ
- */
- CIQ.qs = function (query) {
- var qsParm = {};
- if (!query) query = window.location.search.substring(1);
- var parms = query.split("&");
- for (var i = 0; i < parms.length; i++) {
- var pos = parms[i].indexOf("=");
- var key;
- if (pos > 0) {
- key = parms[i].substring(0, pos);
- qsParm[key] = parms[i].substring(pos + 1);
- } else {
- key = parms[i];
- qsParm[key] = null;
- }
- }
- return qsParm;
- };
-
- /**
- * @callback CIQ.postAjax~requestCallback
- * @param {number} status HTTP status
- * @param {string} response HTTP response
- */
- /**
- * Convenience function for making an ajax post. If payload is non-null then the method will be set to POST, otherwise GET.
- * @param {object} params Parameters for the post
- * @param {string} [params.url] The url to send the ajax query to
- * @param {string} [params.payload] An optional payload to send
- * @param {CIQ.postAjax~requestCallback} [params.cb] Callback function when complete
- * @param {string} [params.contentType] Optionally override the content type
- * @param {boolean} [params.noEpoch] By default the epoch is appended as a query string to bust caching. Set this to false to not append the epoch.
- * @param {string} [params.method] Optionally override the HTTP method
- * @param {object} [params.headers] Optional additional HTTP headers to send. Example: ```{"x-custom-header-1":"abc","x-custom-header-2":"123"}```
- * @param {boolean} [params.responseHeaders] Optional Set to true to have callback passed the response headers from the server
- * @param {number} [params.timeout] Optional Request timeout in msec. If omitted, timeout is default (no timeout)
- * @param {boolean} [params.ungovernable] Optional If true, request not subject to rate limiting
- * @param {string} arg1 Payload
- * @param {function} arg2 Callback
- * @param {string} arg3 Ajax content type
- * @param {boolean} arg4 Set to true for no epoch
- * @return {boolean} True if there were no errors fetching data.
- * @memberof CIQ
- * @since 3.0.0 Added `timeout` and `ungovernable` parameters.
- */
- CIQ.postAjax = function (params, arg1, arg2, arg3, arg4) {
- if (typeof params == "string") {
- params = {
- url: params,
- payload: arg1,
- cb: arg2,
- contentType: arg3,
- noEpoch: arg4,
- method: null,
- responseHeaders: false
- };
- }
- var url = params.url,
- cb = params.cb,
- payload = params.payload;
- if (!cb) cb = function () {};
- if (!params.ungovernable) {
- if (
- CIQ.Extras &&
- CIQ.Extras.RequestLimiter &&
- CIQ.Extras.RequestLimiter.hitRequestLimit(url)
- ) {
- cb(429, null, {});
- return;
- }
- }
- function parseHeaders(server) {
- //Optional code for processing headers.
- var headers = {};
- if (!params.responseHeaders) return;
- var headerString = server.getAllResponseHeaders();
- var headerArray = headerString.split("\n");
- for (var i = 0; i < headerArray.length; i++) {
- var split = headerArray[i].split(":");
- while (split[1] && split[1].charAt(0) == " ")
- split[1] = split[1].substring(1);
- if (split[0] !== "") {
- headers[split.shift()] = split.join(":");
- }
- }
- return headers;
- }
- var server = new XMLHttpRequest();
- if (!server) return false;
- var epoch = new Date();
- if (!params.noEpoch) {
- if (url.indexOf("?") == -1) url += "?ciqrandom=" + epoch.getTime();
- else url += "&ciqrandom=" + epoch.getTime();
- }
- var method = params.method,
- headers = params.headers;
- if (!method) method = payload ? "POST" : "GET";
-
- server.open(method, url, true);
- if (!params.contentType)
- params.contentType = "application/x-www-form-urlencoded";
- if (payload) server.setRequestHeader("Content-Type", params.contentType);
- if (headers) {
- for (var header in headers) {
- server.setRequestHeader(header, headers[header]);
- }
- }
- if (params.timeout) {
- server.timeout = params.timeout; // in msec
- }
- server.ontimeout = function () {
- cb(408, null, {});
- };
- server.onload = function () {
- if (this.status === 0) this.status = "0";
- else if (!this.status) this.status = 200; //XDomainRequest
- cb(this.status, this.responseText, parseHeaders(this));
- };
- server.onerror = function () {
- cb("0", null, {});
- };
- try {
- server.send(payload);
- } catch (e) {
- cb("0", e, {});
- }
- return true;
- };
-
- /**
- * Dynamically load UI elements from an external HTML file. This is accomplished by rendering raw HTML in an `iframe`
- * and then cloning all of the newly created DOM elements into our main document. Repeat requests for the same resource
- * load data from the existing `iframe`.
- *
- * The title of the `iframe` is checked. External content should *not* have a title. By convention, 404 or 500 errors
- * have a title; and so, we use this to determine whether the `iframe` contains valid content or not.
- *
- * @param {string} url The external URL to fetch new UI content.
- * @param {HTMLElement} el Element to append the UI content to; the default is `document.body`.
- * @param {Function} cb A callback function to call when the new UI is available.
- * @memberof CIQ
- * @since
- * - 6.1.0 Added the `el` parameter.
- * - 7.2.0 Added caching per application instance by reusing the `iframe` contents.
- */
- CIQ.loadUI = function (url, el, cb) {
- if (!el || typeof el == "function") {
- cb = el; // backward compatibility
- el = document.body;
- }
- var iframe = document.querySelector('iframe[original-url="' + url + '"]');
- var onload = function () {
- var iframeDocument = null;
-
- try {
- iframeDocument = this.contentDocument;
- } catch (error) {
- return cb(error);
- }
-
- // having a title is considered a server error such as a 404 or 500 response
- if (iframeDocument && !iframeDocument.title) {
- var html = iframeDocument.body.innerHTML;
- var div = document.createElement("div");
-
- CIQ.innerHTML(div, html);
-
- for (var j = 0; j < div.children.length; j++) {
- var ch = div.children[j].cloneNode(true);
- el.appendChild(ch);
- }
-
- cb(null);
- } else {
- cb(new Error("iFrame not found or document has a title"));
- }
- };
-
- if (iframe) {
- var iframeDocument = null;
-
- try {
- iframeDocument = iframe.contentDocument;
- } catch (error) {
- return cb(error);
- }
-
- if (
- iframeDocument.readyState === "complete" &&
- iframeDocument.location &&
- iframeDocument.location.href !== "about:blank"
- ) {
- onload.call(iframe);
- } else {
- iframe.addEventListener("load", onload);
- }
- } else {
- iframe = document.createElement("iframe");
- iframe.setAttribute("original-url", url);
- iframe.src = url + (url.indexOf("?") === -1 ? "?" : "&") + CIQ.uniqueID();
- iframe.hidden = true;
- iframe.addEventListener("load", onload);
- document.body.appendChild(iframe);
- }
- };
-
- /**
- * Loads JavaScript dynamically. Keeps a static memory of scripts that have been loaded to
- * prevent them from being loaded twice. The callback function however is always called, even
- * if the script has already been loaded.
- *
- * @param {string} scriptName The URL of the script to load.
- * @param {function} [cb] Callback function to call when the script is loaded.
- * @param {boolean} [isModule] If true, the script loads a module.
- *
- * @memberof CIQ
- * @since 8.0.0 Added the `isModule` parameter.
- */
- CIQ.loadScript = function (scriptName, cb, isModule) {
- if (!CIQ.loadedScripts) CIQ.loadedScripts = {};
- if (CIQ.loadedScripts[scriptName]) {
- if (cb) cb();
- return;
- }
- var script = document.createElement("SCRIPT");
- if (isModule) {
- script.type = "module";
- script.crossOrigin = "use-credentials";
- } else {
- script.async = true;
- }
- script.onload = function () {
- CIQ.loadedScripts[scriptName] = true;
- if (cb) cb();
- };
- var uniqueName = scriptName;
- // Use the epoch to create a unique query string, which will force the browser to reload
- if (uniqueName.indexOf("?") == -1) {
- uniqueName = uniqueName + "?" + Date.now();
- } else {
- uniqueName = uniqueName + "&" + Date.now();
- }
- script.src = uniqueName;
- var s = document.getElementsByTagName("script")[0];
- if (!s) document.body.append(script);
- else s.parentNode.insertBefore(script, s.nextSibling);
- };
-
- /**
- * Loads a stylesheet.
- * @param {string} stylesheet Name of stylesheet file.
- * @param {Function} cb Function to call when the stylesheet is fully loaded
- * @since 2016-03-11
- * @memberof CIQ
- */
- CIQ.loadStylesheet = function (stylesheet, cb) {
- var lnk = document.createElement("link");
- lnk.rel = "stylesheet";
- lnk.type = "text/css";
- lnk.media = "screen";
- lnk.href =
- stylesheet + (stylesheet.indexOf("?") === -1 ? "?" : "&") + Date.now();
- lnk.onload = function () {
- if (this.loaded) return; //undocumented IE Edge bug, css files load twice. This to prevent double-triggering of onload, which may load html file twice.
- this.loaded = true;
- if (cb) cb();
- };
- var links = document.getElementsByTagName("link");
- var lastLink = links[links.length - 1];
- if (!lastLink) document.head.append(lnk);
- else lastLink.parentNode.insertBefore(lnk, lastLink.nextSibling);
- };
-
- /**
- * Loads a feature function widget. Feature function widgets consist of a CSS file, a
- * JavaScript file, and an HTML file.
- *
- * Use this function to dynamically load content and functionality.
- *
- * @param {string} widget Name of the widget to load. The widget's JavaScript, CSS, and HTML
- * files should have this name.
- * @param {HTMLElement} el Element to which to append the UI content. The default is
- * `document.body`.
- * @param {function} cb Function to call when the widget is fully loaded.
- * @param {boolean} isModule When true, the script loads a module.
- *
- * @memberof CIQ
- * @since
- * - 6.1.0 Added the `el` parameter.
- * - 8.0.0 Added the `isModule` parameter.
- */
- CIQ.loadWidget = function (widget, el, cb, isModule) {
- if (!el || typeof el == "function") {
- cb = el; // backward compatibility
- el = document.body;
- }
- CIQ.loadStylesheet(widget + ".css", function () {
- CIQ.loadUI(widget + ".html", el, function (err) {
- if (err) cb(err);
- else CIQ.loadScript(widget + ".js", cb, isModule);
- });
- });
- };
-
- /**
- * Checks to see if the enabled plugins are done dynamically loading.
- * @param {array} plugins An array of strings that define which plugins to check
- * The plugin names provided must match the following format: if cq-scriptiq is enabled, 'scriptiq' is the plugin name entered into the array
- * @param {Function} cb Function to call when all the plugins are fully loaded
- * @memberof CIQ
- * @since 6.1.0
- */
- CIQ.waitForPlugins = function (plugins, cb) {
- var numPluginsLoaded = 0;
- var numPlugins = plugins.length;
- if (!numPlugins) {
- cb();
- return;
- }
-
- for (var i = 0; i < numPlugins; i++) {
- var tagName = "cq-" + plugins[i];
- var element = document.getElementsByTagName(tagName)[0];
- if (element && element.hasAttribute("loaded")) {
- numPluginsLoaded++;
- }
- }
-
- if (numPlugins !== numPluginsLoaded) {
- return setTimeout(function () {
- CIQ.waitForPlugins(plugins, cb);
- }, 0);
- }
-
- cb();
- };
-
- /**
- * Adds style content to a document if it has not been added already.
- *
- * @param {string} content Style content.
- * @param {string} path Unique identifier, which prevents duplicate style inclusions.
- *
- * @memberof CIQ
- * @since 8.0.0
- */
- CIQ.addInternalStylesheet = function (content, path = "") {
- if (!content) return;
- if (content.default) content = content.default;
- if (typeof content !== "string") return;
- if (path && document.querySelector('style[path="' + path + '"]')) return;
- const el = document.createElement("style");
- el.setAttribute("type", "text/css");
- el.setAttribute("path", path);
- el.innerText = content;
- document.head.appendChild(el);
- };
-
- };
-
-
- let __js_core_engine_accessory_ = (_exports) => {
-
-
- var CIQ = _exports.CIQ;
-
- /**
- * Registers the Chart controls and attaches event handlers to the zoom and home controls.
- * @private
- * @memberof CIQ.ChartEngine
- */
- CIQ.ChartEngine.prototype.registerHTMLElements = function () {
- const c = this.chart.container;
- for (let control in CIQ.ChartEngine.htmlControls) {
- if (
- typeof this.chart[control] == "undefined" &&
- typeof this.controls[control] == "undefined"
- ) {
- if (!this.allowZoom && control == "chartControls") continue;
- let el = c.querySelector("." + control);
- if (el) {
- this.chart[control] = el;
- this.controls[control] = el;
- } else {
- const rawHTML = CIQ.ChartEngine.htmlControls[control];
- if (!rawHTML) continue;
- const div = document.createElement("DIV");
- div.innerHTML = rawHTML;
- el = div.firstChild;
- c.appendChild(el);
- this.chart[control] = el;
- this.controls[control] = el;
- el.classList.add(control);
- }
- }
- }
- const { chartControls, home } = this.controls;
- if (chartControls) {
- const zoomIn = chartControls.querySelector(".stx-zoom-in");
- const zoomOut = chartControls.querySelector(".stx-zoom-out");
-
- CIQ.safeClickTouch(
- zoomIn,
- (function (self) {
- return function (e) {
- if (self.allowZoom) self.zoomIn(e);
- e.stopPropagation();
- };
- })(this)
- );
- CIQ.safeClickTouch(
- zoomOut,
- (function (self) {
- return function (e) {
- if (self.allowZoom) self.zoomOut(e);
- e.stopPropagation();
- };
- })(this)
- );
- if (!CIQ.touchDevice) {
- this.makeModal(zoomIn);
- this.makeModal(zoomOut);
- }
- }
- if (home) {
- CIQ.safeClickTouch(
- home,
- (function (self) {
- return function (e) {
- e.stopPropagation();
- // If we are not in historical mode then scroll home
- if (!self.isHistoricalMode()) {
- self.home({ animate: true });
- return;
- }
- // If in historical mode delete any range the chart might have to prevent setting it again and call loadChart
- // This will be fast than scrolling and paginating forward as the chart progresses towards the current day
- delete self.layout.range;
- self.loadChart(self.chart.symbol, function () {
- self.home({ animate: false });
- });
- };
- })(this)
- );
- if (!CIQ.touchDevice) {
- this.makeModal(home);
- }
- }
- };
-
- /**
- * Returns the chart to the home position, where the most recent tick is on the right side of the screen.
- *
- * By default the home() behavior is to maintain the white space currently on the right side of the chart.
- * To align the chart to the right edge instead, set the white space to 0 by calling: `stxx.home({whitespace:0});` or `stxx.home({maintainWhitespace:false});`
- *
- * If you want to home the chart and also do a full reset of both the x and y axis zoom levels so they revert to the initial default settings, execute this:
- * ```
- * stxx.setCandleWidth(8);stxx.home(0);
- * ```
- *
- * Keep in mind that certain floating labels, such as the `roundRectArrow` will prevent the chart from being flush to the right edge even if the white space is 0.
- * This is to prevent bars from being obstructed by the protruding portion of the label.
- *
- * See {@link CIQ.ChartEngine#getLabelOffsetInPixels} and {@link CIQ.ChartEngine#yaxisLabelStyle} for more details.
- *
- * Used by CIQ.ChartEngine.htmlControls.home.
- *
- * @param {object} params Object containing the following keys:
- * @param {boolean} [params.animate = false] Set to true to animate a smooth scroll to the home position.
- * @param {boolean} [params.maintainWhitespace = true] Set to `true` to maintain the currently visible white space on the right of the chart, or to `false` to align to the right edge.
- * @param {number} [params.whitespace = 0] Override to force a specific amount of whitespace on the right of the chart.
- * This will take precedence over `params.maintainWhitespace`
- * @param {CIQ.ChartEngine.Chart} [params.chart] Chart to scroll home. If not defined, all chart objects will be returned to the home position.
- * @memberof CIQ.ChartEngine
- * @example
- * stxx.home({maintainWhitespace:false});
- */
- CIQ.ChartEngine.prototype.home = function (params) {
- this.swipe.amplitude = 0;
- var layout = this.layout;
- if (typeof params != "object") {
- // backward compatibility
- params = {
- maintainWhitespace: params
- };
- }
-
- function resetPanelZooms(stx) {
- for (var p in stx.panels) {
- var yAxes = stx.panels[p].yaxisLHS.concat(stx.panels[p].yaxisRHS);
- for (var a = 0; a < yAxes.length; a++)
- stx.calculateYAxisMargins(yAxes[a]);
- }
- }
- function scrollToCallback(self, chart, exactScroll) {
- return function () {
- resetPanelZooms(self);
- chart.scroll = exactScroll;
- self.draw();
- };
- }
- if (typeof params.maintainWhitespace == "undefined")
- params.maintainWhitespace = true; // maintain the whitespace unless set to false
-
- this.cancelTouchSingleClick = true;
- if (!this.chart.dataSet || !this.chart.dataSet.length) {
- // to clear out anything that may have been on the screen. Otherwise we still show stale data.
- this.draw();
- return;
- }
- this.micropixels = 0;
- var barsDisplayedOnScreen = Math.floor(this.chart.width / layout.candleWidth);
- for (var chartName in this.charts) {
- var chart = this.charts[chartName];
- if (params.chart && params.chart != chart) continue;
-
- var whitespace = 0;
- if (params.maintainWhitespace && this.preferences.whitespace >= 0)
- whitespace = this.preferences.whitespace;
- if (params.whitespace || params.whitespace === 0)
- whitespace = params.whitespace;
- var leftMargin = this.getLabelOffsetInPixels(chart, layout.chartType);
- if (leftMargin > whitespace) whitespace = leftMargin;
-
- var exactScroll = Math.min(barsDisplayedOnScreen, chart.dataSet.length); // the scroll must be the number of bars you want to see.
- if (this.chart.allowScrollPast) exactScroll = barsDisplayedOnScreen; // If whitespace allowed on left of screen
- this.micropixels =
- this.chart.width - exactScroll * layout.candleWidth - whitespace;
- this.preferences.whitespace = whitespace;
- while (this.micropixels > layout.candleWidth) {
- // If micropixels is larger than a candle then scroll back further
- exactScroll++;
- this.micropixels -= layout.candleWidth;
- }
- while (this.micropixels < 0) {
- exactScroll--;
- this.micropixels += layout.candleWidth;
- }
- this.micropixels -= layout.candleWidth;
- exactScroll++;
- if (!this.mainSeriesRenderer || !this.mainSeriesRenderer.standaloneBars)
- this.micropixels += layout.candleWidth / 2; // bar charts display at beginning of candle
-
- if (params.animate) {
- var self = this;
- this.scrollTo(
- chart,
- exactScroll,
- scrollToCallback(self, chart, exactScroll)
- );
- } else {
- chart.scroll = exactScroll;
- resetPanelZooms(this);
- }
- }
- this.draw();
- };
-
- /**
- * INJECTABLE
- *
- * This method calls {@link CIQ.ChartEngine#updateFloatHRLabel} to draw the label that floats along the Y axis with the
- * current price for the crosshair.
- * It also fills the date in the "stxx.controls.floatDate" (Style: `stx-float-date`) div which floats along the X axis.
- * This is an appropriate place to inject an append method for drawing a heads up display if desired.
- *
- * You can use {@link CIQ.ChartEngine.XAxis#noDraw} and {@link CIQ.ChartEngine.YAxis#noDraw} to hide the floating labels and axis.
- *
- * It uses {@link CIQ.displayableDate} to format the floating label over the x axis, which can be overwritten as needed to achieve the desired results.
- *
- * @memberof CIQ.ChartEngine.AdvancedInjectable#
- * @alias headsUpHR
- * @since 09-2016-19 Only year and month will be displayed in monthly periodicity.
- */
- CIQ.ChartEngine.prototype.headsUpHR = function () {
- if (this.runPrepend("headsUpHR", arguments)) return;
- var panel = this.currentPanel;
- if (!panel) return;
- var chart = panel.chart;
-
- this.updateFloatHRLabel(panel);
- var floatDate = this.controls.floatDate;
- function setFloatDate(val) {
- CIQ.efficientDOMUpdate(floatDate, "innerHTML", val);
- }
-
- if (floatDate && !chart.xAxis.noDraw) {
- var bar = this.barFromPixel(this.cx);
- var prices = chart.xaxis[bar];
- if (prices && prices.DT) {
- setFloatDate(CIQ.displayableDate(this, chart, prices.DT));
- } else if (prices && prices.index) {
- setFloatDate(prices.index);
- } else {
- setFloatDate(""); // there is no date to display
- }
- }
-
- this.runAppend("headsUpHR", arguments);
- };
-
- /**
- * Sets the chart into a modal mode. Crosshairs are hidden and the chart will not respond to click or mouse events. Call this
- * for instance if you are enabling a dialog box and don't want errant mouse activity to affect the chart.
- * @memberof CIQ.ChartEngine
- */
- CIQ.ChartEngine.prototype.modalBegin = function () {
- this.openDialog = "modal";
- this.undisplayCrosshairs();
- };
-
- /**
- * Ends modal mode. See {@link CIQ.ChartEngine#modalBegin}
- * @memberof CIQ.ChartEngine
- */
- CIQ.ChartEngine.prototype.modalEnd = function () {
- this.cancelTouchSingleClick = true;
- this.openDialog = "";
- this.doDisplayCrosshairs();
- };
-
- /**
- * Convenience function to attach a modal on mouse events
- * @param {HTMLElement} Element to attach the modal to
- * @private
- * @memberof CIQ.ChartEngine
- */
- CIQ.ChartEngine.prototype.makeModal = function (element) {
- var self = this;
- element.onmouseover = function (event) {
- self.modalBegin();
- };
- element.onmouseout = function (event) {
- self.modalEnd();
- };
- };
-
- /**
- * INJECTABLE
- *
- * Updates the position of the stxx.controls.floatDate element ( Style: `stx-float-date` ) and calls {@link CIQ.ChartEngine.AdvancedInjectable#headsUpHR} to display the crosshairs labels on both x and y axis.
- * A timer is used to prevent this operation from being called more frequently than once every 100 milliseconds in order to
- * improve performance during scrolling.
- * @memberof CIQ.ChartEngine.AdvancedInjectable#
- * @alias updateChartAccessories
- */
- CIQ.ChartEngine.prototype.updateChartAccessories = function () {
- if (this.accessoryTimer !== null) clearTimeout(this.accessoryTimer);
- if (!CIQ.ChartEngine.drawingLine && CIQ.touchDevice) {
- if (new Date().getTime() - this.lastAccessoryUpdate < 100) {
- this.accessoryTimer = setTimeout(
- (function (stx) {
- return function () {
- stx.updateChartAccessories();
- };
- })(this),
- 10
- );
- return;
- }
- }
- if (!this.chart.dataSet) return;
- if (this.runPrepend("updateChartAccessories", arguments)) return;
- this.accessoryTimer = null;
- this.lastAccessoryUpdate = new Date().getTime();
- var floatDate = this.controls.floatDate;
- if (floatDate) {
- var panel = this.currentPanel;
- if (!panel) panel = this.chart.panel;
- if (panel) {
- var chart = panel.chart;
- var bottom =
- this.xAxisAsFooter === true
- ? 0
- : this.chart.canvasHeight - panel.chart.bottom;
- var halfLabelWidth = floatDate.offsetWidth / 2 - 0.5;
- var l = this.pixelFromTick(this.crosshairTick, chart) - halfLabelWidth;
- if (l < 0) l = 0;
- else if (l > this.width - 2 * halfLabelWidth - 1)
- l = this.width - 2 * halfLabelWidth - 1;
- CIQ.efficientDOMUpdate(floatDate.style, "left", l + "px");
- CIQ.efficientDOMUpdate(floatDate.style, "bottom", bottom + "px");
- }
- }
- this.positionCrosshairsAtPointer();
- this.headsUpHR();
- this.runAppend("updateChartAccessories", arguments);
- };
-
- /**
- * Positions a "sticky" (a tooltip element). It is positioned relative to the cursor but so that it is always available and never
- * accidentally tappable on a touch device.
- * @param {HTMLElement} m The sticky
- * @memberof CIQ.ChartEngine
- */
- CIQ.ChartEngine.prototype.positionSticky = function (m) {
- var top = Math.max(this.cy - m.offsetHeight - 60, 0);
- var right = Math.min(
- this.chart.canvasWidth - (this.cx - 50),
- this.chart.canvasWidth - m.offsetWidth
- );
- m.style.top = top + "px";
- m.style.right = right + "px";
- };
-
- /**
- * Displays the "sticky" (tooltip element). The sticky should be in `CIQ.ChartEngine.controls.mSticky`.
- *
- * To disable stickies, set that element to null. See {@link CIQ.ChartEngine.htmlControls}.
- *
- * To customize, see the [Using and Customizing Drawing Tools](tutorial-Using%20and%20Customizing%20Drawing%20Tools.html#customSticky) tutorial.
- *
- * @param {object} params Optional arguments to pass into the function.
- * @param {string} [params.message] The message to display in the sticky.
- * @param {string} [params.backgroundColor] The background color for the sticky (the foreground color is selected automatically).
- * @param {boolean} [params.forceShow] If true, always shows the sticky (as opposed to only on hover).
- * @param {boolean} [params.noDelete] If true, hides the delete instructions/button.
- * @param {boolean} [params.noEdit] If true, hides the edit instructions/button.
- * @param {string} [params.type] Set to "study","drawing","series", or whatever causes the sticky to be displayed.
- * @param {function} [params.positioner] Sets custom positioning behavior for the sticky. Called with `Function.prototype.call()`,
- * specifying the engine instance as context. Called with one argument, which is a reference to the sticky element.
- * @memberof CIQ.ChartEngine
- * @since
- * - 6.0.0 Consolidated arguments into the `params` object.
- * - 6.3.0 Added the `noEdit` parameter.
- * - 7.4.0 Added the `positioner` parameter.
- */
- CIQ.ChartEngine.prototype.displaySticky = function (params) {
- var m = this.controls.mSticky;
- if (!m) return;
- var mi = m.querySelector(".mStickyInterior");
- if (!mi) return;
- var overlayTrashCan = m.querySelector(".overlayTrashCan");
- var overlayEdit = m.querySelector(".overlayEdit");
- var mouseDeleteInstructions = m.querySelector(".mouseDeleteInstructions");
- var longPressText = m.querySelector(".stickyLongPressText");
- mouseDeleteInstructions.classList.remove("no_edit");
- // backwards compatibility:
- if (!params || typeof params != "object")
- params = {
- message: arguments[0],
- backgroundColor: arguments[1],
- forceShow: arguments[2],
- noDelete: arguments[3],
- type: arguments[4]
- };
- var message = params.message,
- backgroundColor = params.backgroundColor,
- forceShow = params.forceShow,
- noDelete = params.noDelete,
- noEdit = params.noEdit,
- type = params.type;
- if (!forceShow && !message) {
- mi.innerHTML = "";
- m.style.display = "none";
- if (overlayTrashCan) overlayTrashCan.style.display = "none";
- if (overlayEdit) overlayEdit.style.display = "none";
- if (mouseDeleteInstructions) mouseDeleteInstructions.style.display = "none";
- if (longPressText) longPressText.style.display = "none";
- } else {
- if (!message) message = "";
- var defaultColor = this.defaultColor;
- if (backgroundColor == "auto") backgroundColor = defaultColor;
- if (forceShow && !message) {
- mi.style.backgroundColor = "";
- mi.style.color = "";
- mi.style.display = "none";
- } else if (backgroundColor) {
- mi.style.backgroundColor = backgroundColor;
- mi.style.color = CIQ.isTransparent(backgroundColor)
- ? defaultColor
- : CIQ.chooseForegroundColor(backgroundColor);
- mi.style.display = "inline-block";
- } else {
- mi.style.backgroundColor = "";
- mi.style.color = "";
- mi.style.display = "inline-block";
- }
- mi.innerHTML = message;
- var rtClick = m.querySelector(".mStickyRightClick");
- rtClick.className = "mStickyRightClick"; //reset
- if (type) rtClick.classList.add("rightclick_" + type);
- rtClick.style.display = "";
- m.style.display = "inline-block";
- var draggableObject = this.highlightedDraggable; // set by findHighlights
- if (
- !draggableObject ||
- (draggableObject &&
- draggableObject.undraggable &&
- draggableObject.undraggable(this))
- ) {
- longPressText.style.display = "none";
- }
- if (
- noDelete ||
- this.bypassRightClick === true ||
- this.bypassRightClick[type]
- ) {
- rtClick.style.display = "none";
- } else if (this.highlightViaTap || this.touches.length) {
- if (overlayTrashCan) overlayTrashCan.style.display = "inline-block";
- if (overlayEdit && !noEdit) overlayEdit.style.display = "inline-block";
- if (mouseDeleteInstructions)
- mouseDeleteInstructions.style.display = "none";
- if (longPressText) longPressText.style.display = "none";
- m.classList[message === "" ? "add" : "remove"]("hide");
- } else {
- if (noEdit) mouseDeleteInstructions.classList.add("no_edit");
- if (mouseDeleteInstructions) {
- mouseDeleteInstructions.style.display = "block";
- }
- if (longPressText) {
- longPressText.style.display = "none";
- var drag = this.preferences.dragging;
- if (drag && params.panel && !params.panel.noDrag) {
- if ((drag === true || drag.study) && type == "study")
- longPressText.style.display = "block";
- else if ((drag === true || drag.series) && type == "series")
- longPressText.style.display = "block";
- }
- }
- }
-
- var stickyType = type || "default";
- m.setAttribute("cq-sticky-type", stickyType);
-
- var positionSticky = params.positioner || this.positionSticky;
- positionSticky.call(this, m);
- }
- };
-
- /**
- * Adds a message to the chart.
- *
- * Creates a `div` containing a text message. Appends the `div` to the
- *
- * CIQ.ChartEngine.htmlControls.notificationTray.
- *
- * Notifications can be interactive (see the `callback` and `dismissalListeners` parameters),
- * and they can be queried by their names, which are set as class names on the
- * notification `div`.
- *
- * @param {string} name The name of the notification, which is added to the class list of the
- * notification `div`.
- * @param {string} message Text to display in the notification `div`.
- * @param {object} [params] Configuration parameters.
- * @param {function} [params.callback] Added to the notification `div` as a listener for the
- * "pointer up" event.
- * @param {Array} [params.dismissalListeners] Array of event listeners added to the
- * notification.
- * @param {string} params.dismissalListeners.type The listener event type. See
- * {@link CIQ.ChartEngine#addEventListener}.
- * @param {function} params.dismissalListeners.callback The listener callback function.
- *
- * @memberof CIQ.ChartEngine
- * @since 8.0.0
- */
- CIQ.ChartEngine.prototype.displayNotification = function (
- name,
- message,
- params = {}
- ) {
- if (!this.controls.notificationTray) return;
-
- const { callback, dismissalListeners } = params;
- const notificationTray = this.controls.notificationTray;
- let fragment = notificationTray
- .querySelector("template")
- .content.cloneNode(true);
- const notification = fragment.firstElementChild;
-
- notification.className = name;
- notification.querySelector(".message").textContent = message;
-
- if (callback) {
- // iOS version < 13.2 and some older browsers don't support pointer events.
- // Fallback to touch events in these cases.
- let leaveHandler = window.PointerEvent ? "pointerup" : "touchend";
- notification.handler = notification.addEventListener(
- leaveHandler,
- callback
- );
- }
-
- if (dismissalListeners) {
- notification.listeners = {};
- dismissalListeners.forEach((listener) => {
- notification.listeners[name] = this.addEventListener(
- listener.type,
- listener.callback
- );
- });
- }
-
- this.makeModal(notification);
-
- notificationTray.appendChild(notification);
- };
-
- /**
- * Removes a notification from the
- *
- * CIQ.ChartEngine.htmlControls.notificationTray.
- *
- * @param {string} name The name of the notification that is removed.
- *
- * @memberof CIQ.ChartEngine
- * @since 8.0.0
- */
- CIQ.ChartEngine.prototype.removeNotification = function (name) {
- if (!this.controls.notificationTray) return;
-
- const notificationTray = this.controls.notificationTray;
- let notification = notificationTray.querySelector(`.${name}`);
-
- if (notification) {
- if (notification.handler)
- notification.removeEventListener(notification.handler);
- if (notification.listeners) {
- for (const listener in notification.listeners) {
- this.removeEventListener(notification.listeners[listener]);
- }
- }
- this.modalEnd();
- notificationTray.removeChild(notification);
- }
- };
-
- /**
- * INJECTABLE
- *
- * Sets the innerHTML value of the `.mMeasure` HTML DOM Node to contain a measurement (price differential and bars/line distance), usually when a user hovers over a drawing.
- * It is also used to display measurement as a drawing is being created or when using the 'Measure' tool.
- *
- * It also sets `this.controls.mSticky` with the measurement and displays it on `mSticky` on hover.
- *
- * Example: 23.83 (-12%) 11 Bars
- *
- * It requires the UI to include the following div: ```false
if you want to skip price and percentage display
- * @param {number} tick1 Beginning tick of the drawing
- * @param {number|boolean} tick2 Ending tick of the drawing, pass false
if you want to skip tick count display
- * @param {boolean} hover True to turn on the measurement, false to turn it off
- * @param {string} [name] Name of drawing, not used by default but passed into injection
- * @memberof CIQ.ChartEngine
- * @since
- * - 4.0.0 Added name argument.
- * - 6.0.0 Allow price2 and tick2 to be false, skipping the respective display.
- * @example
- * // Measuring tool styling CSS sample
- * .currentMeasure {
- * text-align: left;
- * display: inline-block;
- * margin: 4px 0 0 20px;
- * height: 20px;
- * line-height: 20px;
- * }
-
- * .mMeasure {
- * display: inline-block;
- * margin: 0 0 0 0;
- * overflow: hidden;
- * text-overflow: ellipsis;
- * white-space: nowrap;
- * width:140px;
- * }
- * @example
- * // This is an example of the framework to use for writing a prepend to further manipulate/display the measurements
- * CIQ.ChartEngine.prototype.prepend("setMeasure",function() {
- *
- * var m = document.querySelector(".mMeasure");
- *
- * if (!m) return; // Can't show a measurement if the div is not present.
- *
- * // Add your logic to manage the display of the measurements (price1, price2, tick1, tick2).
- * //*****************************************
- * var message = 'blah measurement';
- * //*****************************************
- *
- * m.innerHTML = message;
- *
- * if (this.activeDrawing) return; // Don't show measurement Sticky when in the process of drawing.
- *
- * m = this.controls.mSticky;
- * if (m) {
- * var mStickyInterior = m.querySelector(".mStickyInterior");
- * if (hover) {
- * m.style.display = "inline-block";
- * mStickyInterior.style.display = "inline-block";
- * if(price1) {
- * mStickyInterior.innerHTML = message;
- * }
- * this.positionSticky(m);
- * } else {
- * m.style.display = "none";
- * mStickyInterior.innerHTML = "";
- * }
- * }
- *
- * //return true; // If you don't want to continue into the regular function.
- * //return false; // If you want to run through the standard function once you are done with your custom code.
- * });
- */
- CIQ.ChartEngine.prototype.setMeasure = function (
- price1,
- price2,
- tick1,
- tick2,
- hover,
- name
- ) {
- if (this.runPrepend("setMeasure", arguments)) return;
- var m = (this.drawingContainer || document).querySelector(".mMeasure");
- var message = "";
- if (!price1 && price1 !== 0) {
- if (!this.anyHighlighted && this.currentVectorParameters.vectorType === "")
- this.clearMeasure();
- } else {
- if (price2 !== false) {
- var distance =
- Math.round(Math.abs(price1 - price2) * this.chart.roundit) /
- this.chart.roundit;
- distance = distance.toFixed(this.chart.yAxis.printDecimalPlaces);
- if (this.internationalizer) {
- message += this.internationalizer.numbers.format(distance);
- } else {
- message += distance;
- }
- var pct;
- if (price1 > 0 && price2 > 0) {
- pct = (price2 - price1) / price1;
- if (Math.abs(pct) > 0.1) {
- pct = Math.round(pct * 100);
- } else if (Math.abs(pct) > 0.01) {
- pct = Math.round(pct * 1000) / 10;
- } else {
- pct = Math.round(pct * 10000) / 100;
- }
- if (this.internationalizer) {
- pct = this.internationalizer.percent.format(pct / 100);
- } else {
- pct = pct + "%";
- }
- message += " (" + pct + ")";
- }
- }
- if (tick2 !== false) {
- var ticks = Math.abs(tick2 - tick1);
- ticks = Math.round(ticks) + 1;
- var barsStr = this.translateIf("Bars");
- message += " " + ticks + " " + barsStr;
- }
-
- if (m) m.innerHTML = message;
- }
-
- if (this.activeDrawing) return; // Don't show measurement Sticky when in the process of drawing
- m = this.controls.mSticky;
- if (m) {
- var mStickyInterior = m.querySelector(".mStickyInterior");
- if (hover) {
- m.style.display = "inline-block";
- mStickyInterior.style.display = "inline-block";
- if (price1 || price1 === 0) {
- mStickyInterior.innerHTML = message;
- }
- m.classList[message === "" ? "add" : "remove"]("hide");
- this.positionSticky(m);
- } else {
- m.style.display = "none";
- mStickyInterior.innerHTML = "";
- }
- }
- this.runAppend("setMeasure", arguments);
- };
-
- /**
- * Clears the innerHTML value of the `.mMeasure` HTML DOM Node.
- * @memberof CIQ.ChartEngine
- */
- CIQ.ChartEngine.prototype.clearMeasure = function () {
- var m = (this.drawingContainer || document).querySelector(".mMeasure");
- if (m) m.innerHTML = "";
- };
-
- /**
- * Effects a zoom from either zoomIn() or zoomOut(). Called from an EaseMachine
- * @param {number} candleWidth The new candleWidth
- * @param {CIQ.ChartEngine.Chart} chart The chart to center
- * @memberof CIQ.ChartEngine
- * @since
- * - 4.0.0 Will maintain tick position near the cursor if CIQ.ChartEngine.preferences.zoomAtCurrentMousePosition is `true`.
- * - 4.1.0 Will keep left edge stable and zoom to the right when white space is present on the left.
- */
- CIQ.ChartEngine.prototype.zoomSet = function (candleWidth, chart) {
- candleWidth = this.constrainCandleWidth(candleWidth);
- if (this.chart.tempCanvas.style.display != "none")
- CIQ.clearCanvas(this.chart.tempCanvas, this);
- var mainSeriesRenderer = this.mainSeriesRenderer || {};
- if (!mainSeriesRenderer.params || !mainSeriesRenderer.params.volume) {
- var maintainTick;
- if (
- this.preferences.zoomAtCurrentMousePosition &&
- this.zoomInitiatedByMouseWheel &&
- this.crosshairTick < chart.dataSet.length
- ) {
- // keep the bar near the cursor stable
- // at chart load it is possible for this.crosshairTick to be null (refresh while cursor is in the xAxis margin)
- maintainTick = this.crosshairTick || this.tickFromPixel(this.cx, chart);
- } else if (this.isHome()) {
- // keep right edge stable and zoom to the left
- maintainTick = chart.dataSet.length - 1;
- } else if (this.chart.scroll > this.chart.dataSet.length) {
- // keep left edge stable and zoom to the right
- maintainTick = 0;
- } else if (this.grabMode == "zoom-x") {
- // keep right edge stable and zoom to the left
- maintainTick = this.tickFromPixel(this.chart.width, chart);
- } else {
- // keep the center bar in the center and zoom equally left and right
- maintainTick = this.tickFromPixel(this.chart.width / 2, chart);
- }
- if (this.animations.zoom.hasCompleted) {
- this.zoomInitiatedByMouseWheel = false;
- }
- // this is the code that keeps the chart's position stable.
- // Bypassing this code will cause the chart's left position to remain stable
- // which is really the only way to get a smooth zoom for variable width candles (because the act of scrolling inherently changes the number of candles that fit on the screen)
- var distanceFromFront = chart.dataSet.length - 1 - maintainTick;
- var oldScroll = chart.scroll;
- chart.scroll =
- Math.round(
- (this.pixelFromTick(maintainTick, chart) - chart.left) / candleWidth
- ) +
- 1 +
- distanceFromFront;
- this.micropixels +=
- (oldScroll - distanceFromFront) * this.layout.candleWidth -
- (chart.scroll - distanceFromFront) * candleWidth;
- }
- this.setCandleWidth(candleWidth);
- chart.spanLock = false;
- this.draw();
- this.doDisplayCrosshairs();
- this.updateChartAccessories();
- };
-
- };
-
-
- let __js_core_engine_baselines_ = (_exports) => {
-
-
- var CIQ = _exports.CIQ;
- /**
- * A reference to the renderer of the baseline whose handle is currently selected.
- *
- * The baseline handle can be accessed from {@link CIQ.ChartEngine#baselineHelper}.
- *
- * @type {CIQ.Renderer}
- * @memberof CIQ.ChartEngine
- * @since 8.2.0
- */
- CIQ.ChartEngine.prototype.currentBaseline = null;
-
- /**
- * Baseline helper for the chart engine.
- *
- * Maps renderers to value objects that contain data related to the baseline, including the
- * baseline handle (a reference to the DOM element that functions as the handle).
- *
- * @type {MapCIQ.ChartEngine.htmlControls[`baselineHandle`]
; otherwise,
- * creates a baseline handle DOM element and adds a reference to the DOM element to the value
- * object and to the chart controls object, {@link CIQ.ChartEngine.htmlControls}. The handle is
- * accessed in the chart controls object by a property name that is the concatenation of the
- * renderer name and "cq-baseline-handle", for example:
- * ```
- * stxx.controls[`${renderer.params.name} cq-baseline-handle`];
- * ```
- *
- * @param {CIQ.Renderer} renderer The renderer to register as the key of the baseline helper.
- *
- * @memberof CIQ.ChartEngine
- * @since 8.2.0
- */
- CIQ.ChartEngine.prototype.registerBaselineToHelper = function (renderer) {
- if (!renderer.params.baseline) return;
- const { baselineHelper } = this;
- const self = this;
- if (!baselineHelper.has(renderer)) {
- const { name } = renderer.params;
- let defaultHandle = this.controls.baselineHandle;
- baselineHelper.set(renderer, {
- styleCache: null,
- display: false,
- handle:
- name === "_main_series" && defaultHandle
- ? defaultHandle
- : createHandle(name)
- });
- }
-
- function createHandle(name) {
- name = name.replace(" ", "_");
- const handle = document.createElement("cq-baseline-handle");
- handle.classList.add("stx-baseline-handle", name);
- self.container.append(handle);
- self.controls[`${name} cq-baseline-handle`] = handle;
- return handle;
- }
- };
-
- /**
- * Removes a renderer from {@link CIQ.ChartEngine#baselineHelper}.
- *
- * If the renderer is not the renderer of the main series, removes the baseline handle associated
- * with the renderer from the chart controls object, {@link CIQ.ChartEngine.htmlControls} (see
- * also {@link CIQ.ChartEngine#registerBaselineToHelper}).
- *
- * @param {CIQ.Renderer} renderer The renderer to remove from the baseline helper.
- *
- * @memberof CIQ.ChartEngine
- * @since 8.2.0
- */
- CIQ.ChartEngine.prototype.removeBaselineFromHelper = function (renderer) {
- const { baselineHelper } = this;
- if (baselineHelper.has(renderer)) {
- const name = renderer.params.name.replace(" ", "_");
- if (name !== "_main_series") {
- let handle = baselineHelper.get(renderer).handle;
- delete this.controls[`${name} cq-baseline-handle`];
- this.container.removeChild(handle);
- }
- baselineHelper.delete(renderer);
- }
- };
-
- /**
- * Checks an emitted event to determine whether a baseline handle DOM element is the event target
- * or is in the
- *
- * composed path of the event. If so, sets {@link CIQ.ChartEngine#currentBaseline} to the
- * renderer of the baseline positioned by the handle.
- *
- * @param {Event} e The event that is checked to determine whether a baseline handle is the event
- * target or is in the propagation path of the event.
- * @param {boolean} grabStart If true (and a baseline handle is the event target or is in the
- * event path), baseline repositioning is initiated.
- * @return {boolean} True if a baseline handle is the event target or is in the path of the event,
- * otherwise false.
- *
- * @memberof CIQ.ChartEngine
- * @since 8.2.0
- */
- CIQ.ChartEngine.prototype.findBaselineHandle = function (e, grabStart) {
- for (const baseline of this.baselineHelper) {
- const [renderer, values] = baseline;
- const { handle } = values;
- if (
- e.target === handle ||
- (e.composedPath && e.composedPath().includes(handle))
- ) {
- if (grabStart) {
- this.repositioningBaseline = { lastDraw: Date.now(), handle, renderer };
- handle.classList.add("stx-grab");
- }
- this.currentBaseline = renderer;
- return true;
- }
- }
- return false;
- };
-
- /**
- * Sets `baseline.actualLevel` for any line renderers that are attached to the chart. (See the
- * `baseline` parameter of {@link CIQ.Renderer.Lines}, which may be type
- * {@link CIQ.ChartEngine.Chart#baseline}.)
- *
- * **Note:** Does not set
- * CIQ.ChartEngine.Chart#baseline[`actualLevel`]; that is done in
- * {@link CIQ.ChartEngine.AdvancedInjectable#createDataSegment}.
- *
- * @param {CIQ.ChartEngine.Chart} chart Chart for which the renderer baseline levels are set.
- * @memberof CIQ.ChartEngine
- * @since 8.1.0
- */
- CIQ.ChartEngine.prototype.setBaselines = function (chart) {
- if (!chart) chart = this.chart;
- const self = this;
- const { baselineHelper } = this;
- baselineHelper.forEach(function (values, renderer) {
- let { baseline } = renderer.params;
- const useMain = baseline === true;
- if (useMain) baseline = chart.baseline;
- let { defaultLevel, userLevel } = baseline;
- const yAxis = renderer.getYAxis(self);
- // When interacting with the chart, occasionally yAxis or panel parameter not up to date b/c it we are currently being modifying something.
- // In this case return, to let the modifications finish and the final draw call will correct everything.
- if (!yAxis) return;
- const yBaselineRenderer = self.getYAxisBaselineRenderer(yAxis);
- // Default to the first series on the active renderer of a yAxis
- const id =
- yBaselineRenderer &&
- yBaselineRenderer != self.mainSeriesRenderer &&
- yBaselineRenderer.seriesParams.length &&
- yBaselineRenderer.seriesParams[0].id;
-
- baseline.actualLevel =
- userLevel || userLevel === 0 ? userLevel : defaultLevel;
- if (!baseline.actualLevel && baseline.actualLevel !== 0)
- baseline.actualLevel = calculateActualLevel(id, useMain);
-
- values.display = yBaselineRenderer === renderer ? true : false;
- baselineHelper.set(renderer, values);
- });
-
- function calculateActualLevel(id, useMain) {
- const { dataSegment, dataSet, defaultPlotField } = chart;
- let field = defaultPlotField;
- if (!useMain) field = id;
- let position = self.getFirstLastDataRecord(dataSegment, "tick").tick;
-
- while (true) {
- const quote = dataSet[position];
- if (quote) {
- if (!useMain || field != "Close") {
- const q1 = dataSet[position - 1];
- if (q1 && (q1[field] || q1[field] === 0)) {
- const q = q1[field];
- return typeof q === "object" ? q[defaultPlotField] : q;
- }
- } else if (quote.iqPrevClose || quote.iqPrevClose === 0) {
- return quote.iqPrevClose;
- }
- }
- position--;
- if (position < 0) break;
- }
- }
- };
-
- /**
- * Sets the userLevel of the baseline; that is, the position of the baseline as it being
- * repositioned by the user (see CIQ.ChartEngine.Chart#baseline[`userLevel`]).
- *
- * @memberof CIQ.ChartEngine
- * @private
- * @since 8.2.0
- */
- CIQ.ChartEngine.prototype.setBaselineUserLevel = function () {
- const { chart, currentPanel: panel } = this;
- const { lastDraw, renderer } = this.repositioningBaseline;
-
- if (renderer.params.panel != panel.name) return;
-
- const { baseline: defaultBaseline } = chart;
- const baseline =
- typeof renderer.params.baseline === "object"
- ? renderer.params.baseline
- : defaultBaseline;
- const rAxis = renderer.getYAxis(this);
- const value = this.valueFromPixel(
- this.backOutY(CIQ.ChartEngine.crosshairY),
- panel,
- rAxis
- );
-
- baseline.userLevel = this.adjustIfNecessary(panel, this.crosshairTick, value);
-
- if (Date.now() - lastDraw > 100) {
- this.draw();
- this.repositioningBaseline.lastDraw = Date.now();
- }
- };
-
- /**
- * Sets the minimum and maximum values for a y-axis based on the position of the baseline
- * associated with the axis.
- *
- * @param {number[]} minMax A tuple representing the minimum and maximum values in `dataSegment`.
- * @param {CIQ.ChartEngine.YAxis} yAxis The y-axis for which the minimum and maximum values are
- * set.
- * @return {number[]} A tuple representing the minimum and maximum values of `yAxis`.
- *
- * @memberof CIQ.ChartEngine
- * @private
- * @since 8.2.0
- */
- CIQ.ChartEngine.prototype.setBaselineMinMax = function (minMax, yAxis) {
- const { baselineHelper, chart, repositioningBaseline } = this;
- const { baseline: defaultBaseline, seriesRenderers } = chart;
- const doTransform = chart.transformFunc && yAxis === chart.panel.yAxis;
-
- const baselineToDisplay = yAxis.renderers.find((name) => {
- return baselineHelper.get(seriesRenderers[name]);
- });
-
- if (!baselineToDisplay) return minMax; // No baselines found
-
- let { baseline, type } = seriesRenderers[baselineToDisplay].params;
- if (type === "mountain") return minMax;
-
- baseline = typeof baseline === "object" ? baseline : defaultBaseline;
- let { actualLevel } = baseline;
- if (actualLevel || actualLevel === 0) {
- if (doTransform)
- actualLevel = chart.transformFunc(this, chart, actualLevel);
- const diff = Math.max(actualLevel - minMax[0], minMax[1] - actualLevel);
- minMax[0] = repositioningBaseline ? yAxis.lowValue : actualLevel - diff;
- minMax[1] = repositioningBaseline ? yAxis.highValue : actualLevel + diff;
- }
- return minMax;
- };
-
- /**
- * Positions a baseline handle within the chart area.
- *
- * @param {CIQ.Renderer} renderer The renderer that renders the baseline.
- *
- * @memberof CIQ.ChartEngine
- * @since 8.2.0
- */
- CIQ.ChartEngine.prototype.positionBaselineHandle = function (renderer) {
- if (!this.manageTouchAndMouse) return;
- const { baselineHelper, chart, panels } = this;
- let { baseline, panel: panelName } = renderer.params;
- const yAxis = renderer.params.yAxis || renderer.getYAxis(this);
- let { display: displayed, handle, styleCache } = baselineHelper.get(renderer);
-
- if (baseline === true) baseline = chart.baseline;
- if (baseline.userLevel === false || !displayed) {
- handle.style.display = "none";
- return;
- }
- const panel = panels[panelName];
- const grabbed = handle.classList.contains("stx-grab");
- let display = "block";
-
- let price = baseline.actualLevel;
- if (chart.transformFunc) price = chart.transformFunc(this, chart, price);
- if (price > yAxis.high) {
- price = yAxis.high;
- if (!grabbed) display = "none";
- } else if (price < yAxis.low) {
- price = yAxis.low;
- if (!grabbed) display = "none";
- }
-
- // If chart has been transformed, transform it back or it will be transformed twice!
- if (chart.untransformFunc) price = chart.untransformFunc(this, chart, price);
-
- const basePixel = this.pixelFromPrice(price, panel, yAxis);
- if (!styleCache) styleCache = getComputedStyle(handle);
- const width = CIQ.stripPX(styleCache.width);
-
- let top = `${basePixel - CIQ.stripPX(styleCache.height) / 2}px`;
-
- let left;
- let buffer = this.baselineHandleBuffer || 2;
- let rightIndex = panel.yaxisRHS.indexOf(yAxis) + 1;
- if (rightIndex) {
- let pad = rightIndex === 1 ? buffer : buffer * rightIndex;
- left = `${chart.right - width * rightIndex - pad}px`;
- } else {
- let leftIndex = panel.yaxisLHS.slice(0).reverse().indexOf(yAxis) + 1;
- let pad = leftIndex === 1 ? buffer : buffer * leftIndex;
- left = `${chart.left + width * leftIndex + pad - width}px`;
- }
- Object.assign(handle.style, { display, top, left });
- };
-
- /**
- * Gets the baseline renderer associated with a y-axis.
- *
- * Since a y-axis can only have one baseline associated with it, this function searches the
- * renderers property of the axis, checking for the first renderer that matches an entry in
- * {@link CIQ.ChartEngine#baselineHelper}.
- *
- * @param {CIQ.ChartEngine.YAxis} yAxis The y-axis whose list of renderers is checked for a
- * baseline renderer.
- * @return {CIQ.Renderer|null} The y-axis renderer that renders a baseline or, if a baseline
- * renderer is not associated with the y-axis, null.
- *
- * @memberof CIQ.ChartEngine
- * @since 8.2.0
- */
- CIQ.ChartEngine.prototype.getYAxisBaselineRenderer = function (yAxis) {
- if (!yAxis.renderers.length) return null;
- const { baselineHelper, chart } = this;
-
- let name = yAxis.renderers.find((name) => {
- return baselineHelper.get(chart.seriesRenderers[name]);
- });
-
- if (!name) return null;
- return chart.seriesRenderers[name];
- };
-
- /**
- * Gets the baseline object for a y-axis associated with a baseline.
- *
- * A y-axis can be associated with only one baseline; and so, can have only one baseline renderer
- * and one baseline object.
- *
- * @param {CIQ.ChartEngine.YAxis} yAxis A y-axis associated with a baseline.
- * @returns {object} The baseline object of the y-axis baseline renderer if the y-axis has a
- * baseline renderer and the baseline parameter of the renderer is an object; otherwise,
- * the default chart baseline object, {@link CIQ.ChartEngine.Chart#baseline}.
- *
- * @memberof CIQ.ChartEngine
- * @since 8.2.0
- *
- * @see {@link CIQ.ChartEngine#getYAxisBaselineRenderer}
- */
- CIQ.ChartEngine.prototype.getYAxisBaseline = function (yAxis) {
- const { baseline: defaultBaseline } = this.chart;
- const baselineRenderer = this.getYAxisBaselineRenderer(yAxis);
-
- if (!baselineRenderer) return defaultBaseline;
- const { baseline } = baselineRenderer.params;
- return typeof baseline === "object" ? baseline : defaultBaseline;
- };
-
- };
-
-
- let __js_core_engine_chart_ = (_exports) => {
-
-
- var CIQ = _exports.CIQ;
-
- /**
- * Defines an object used for rendering a chart and is automatically created by the {@link CIQ.ChartEngine}.
- * Chart objects contain the data and config for each chart but they don't actually exist on the screen until a panel is attached.
- * A chart object is attached to both the main chart panel and any related study panels so they can share the same chart data.
- *
- * Example: stxx.panels['chart'].chart
- *
- * Example: stxx.chart (convenience shortcut for accessing the main chart object - same as above)
- *
- * Example stxx.panels['Aroon (14)'].chart
- *
- * @constructor
- * @name CIQ.ChartEngine.Chart
- */
- CIQ.ChartEngine.Chart = function () {
- this.xAxis = new CIQ.ChartEngine.XAxis();
- this.yAxis = new CIQ.ChartEngine.YAxis();
- this.symbolObject = { symbol: null };
- this.series = {};
- this.seriesRenderers = {};
- this.xaxis = [];
- this.state = {};
- this.endPoints = {};
- this.defaultChartStyleConfig = {};
- this.baseline = CIQ.clone(this.baseline); // copy from prototype
- /**
- * @type CIQ.ChartEngine.Panel
- * @memberof CIQ.ChartEngine.Chart#
- */
- this.panel = undefined;
- };
-
- CIQ.extend(
- CIQ.ChartEngine.Chart.prototype,
- {
- /**
- * The current symbol for the chart
- * @type string
- * @memberof CIQ.ChartEngine.Chart#
- */
- symbol: null,
- /**
- * The current symbolObject for the chart. Generally this is simply `{symbol: symbol}`.
- * This is initialized by {@link CIQ.ChartEngine#loadChart}.
- * @type {object}
- * @memberof CIQ.ChartEngine.Chart#
- */
- symbolObject: { symbol: null },
- /**
- * Set this to preset an alternate name for the symbol on the chart label and comparison legend.
- * You can set `stxx.chart.symbolDisplay='yourName'; ` right before calling `loadChart()`.
- * Alternatively, a good place to set it is in your fetch function, if using {@link quotefeed}. See example.
- * @type string
- * @default
- * @memberof CIQ.ChartEngine.Chart#
- * @example
- * // on your initial data fetch call add the following
- * params.stx.chart.symbolDisplay='yourName for '+params.symbol;
- */
- symbolDisplay: null,
- /**
- * Contains information about the series that are associated with the chart.
- * Series are additional data sets, such as used for comparison charts.
- * Note that a series may have a different y-axis calculation than the price chart.
- * See the "parameters" section of {@link CIQ.ChartEngine#addSeries} for details
- * @type {object}
- * @memberof CIQ.ChartEngine.Chart#
- */
- series: {},
- /**
- * Contains "renderers" that are used to create the visualizations for series.
- *
- * Renderers will be drawn in their object order, which can be altered if needed to force certain renderings to be drawn before or after others. See example.
- *
- * @type {object}
- * @memberof CIQ.ChartEngine.Chart#
- * @example
- *
If you are simply setting the customChart object in-line, rather than using it as part of an AP injection into the animation loop, it may be necessary to call `setMainSeriesRenderer` to immediately display results.
- *
To restore the original chart settings, set this object to null (and call setMainSeriesRenderer() if necessary).
- *
- * See {@tutorial Chart Styles and Types} for more details.
- * @type object
- * @default
- * @memberof CIQ.ChartEngine.Chart#
- * @example
- * If no transformation is present, both this method and {@link CIQ.ChartEngine#pixelFromPrice} will return the same value.
- * @param {number} price The transformed price
- * @param {CIQ.ChartEngine.Panel} [panel] The panel (defaults to the chart)
- * @param {CIQ.ChartEngine.YAxis} [yAxis] The yAxis to use
- * @return {number} The Y pixel value
- * @memberof CIQ.ChartEngine
- * @since 4.0.0
- */
- CIQ.ChartEngine.prototype.pixelFromTransformedValue = function (
- price,
- panel,
- yAxis
- ) {
- if (!panel) panel = this.chart.panel;
- var yax = yAxis ? yAxis : panel.yAxis;
- var y = (yax.high - price) * yax.multiplier;
- if (yax.semiLog) {
- var p = Math.max(price, 0);
- var logPrice = Math.log(p) / Math.LN10;
- //if(price<=0) logPrice=0;
- var height = yax.height;
- y = height - (height * (logPrice - yax.logLow)) / yax.logShadow;
- }
- y = yax.flipped ? yax.bottom - y : yax.top + y;
- return y;
- };
-
- /**
- * Returns the Y pixel from a price, even if a transformation such as a percentage change comparison scale is active.
- *
- * To do this, the active transformation function will be applied to the provided price and then {@link CIQ.ChartEngine#pixelFromTransformedValue} will be called on the resulting value.
- * If no transformation is present, both this method and {@link CIQ.ChartEngine#pixelFromTransformedValue} will return the same value.
- * @param {number} price The price or value
- * @param {CIQ.ChartEngine.Panel} panel A panel object (see {@link CIQ.ChartEngine#pixelFromPrice})
- * @param {CIQ.ChartEngine.YAxis} [yAxis] The yaxis to use
- * @return {number} The y axis pixel location
- * @memberof CIQ.ChartEngine
- */
- CIQ.ChartEngine.prototype.pixelFromPrice = function (price, panel, yAxis) {
- if (!panel) panel = this.chart.panel;
- if (this.charts[panel.name] && panel.chart.transformFunc) {
- if (!yAxis || yAxis == panel.yAxis) {
- price = panel.chart.transformFunc(this, panel.chart, price, yAxis); // transform should move to panel
- }
- }
- return this.pixelFromTransformedValue(price, panel, yAxis);
- };
-
- /**
- * Returns the Y pixel location for the (split) unadjusted price rather than the displayed price.
- * This is important for drawing tools or any other device that requires the actual underlying price.
- *
- * @param {CIQ.ChartEngine.Panel} panel The panel to get the value from
- * @param {number} tick The tick location (in the dataSet) to check for an adjusted value
- * @param {number} value The value
- * @param {CIQ.ChartEngine.YAxis} [yAxis] The yaxis to use
- * @return {number} The pixel location
- * @memberof CIQ.ChartEngine
- */
- CIQ.ChartEngine.prototype.pixelFromValueAdjusted = function (
- panel,
- tick,
- value,
- yAxis
- ) {
- // If we're not showing unadjusted quotes, or if the panel isn't a chart then bypass
- if (this.layout.adj || !this.charts[panel.name])
- return this.pixelFromPrice(value, panel, yAxis);
- var a = Math.round(tick); // Not sure why we're rounding this. Possible legacy code.
- // Adjust if there's a ratio attached to the tick
- var ratio;
- if (
- a > 0 &&
- a < panel.chart.dataSet.length &&
- (ratio = panel.chart.dataSet[a].ratio)
- ) {
- return this.pixelFromPrice(value * ratio, panel, yAxis);
- }
- // Otherwise pass through
- return this.pixelFromPrice(value, panel, yAxis);
- };
-
- /**
- * Returns the unadjusted value for a given value, if an adjustment (split) had been applied. This can return a value
- * relative to the original closing price.
- * @param {CIQ.ChartEngine.Panel} panel The panel to check
- * @param {number} tick The location in the dataset
- * @param {number} value The value to adjust
- * @return {number} The adjusted value
- * @memberof CIQ.ChartEngine
- */
- CIQ.ChartEngine.prototype.adjustIfNecessary = function (panel, tick, value) {
- if (this.layout.adj) return value; // Already adjusted prices
- if (!panel || !this.charts[panel.name]) return value;
- var a = Math.round(tick);
- var ratio;
- if (
- a > 0 &&
- a < panel.chart.dataSet.length &&
- (ratio = panel.chart.dataSet[a].ratio)
- ) {
- return value / ratio;
- }
- return value;
- };
-
- };
-
-
- let __js_core_engine_crosshair_ = (_exports) => {
-
-
- var CIQ = _exports.CIQ;
-
- /**
- * INJECTABLE
- *
- * Positions the crosshairs at the last known mouse/finger pointer position, which ensures that
- * the crosshairs are at a known position on touch devices.
- *
- * Called by the {@link WebComponents.cq-toolbar} (drawing toolbar) web component.
- *
- * @alias positionCrosshairsAtPointer
- * @memberof CIQ.ChartEngine.AdvancedInjectable#
- */
- CIQ.ChartEngine.prototype.positionCrosshairsAtPointer = function () {
- var currentPanel = this.currentPanel;
- if (!currentPanel) return;
- if (
- !this.manageTouchAndMouse ||
- (this.mainSeriesRenderer && this.mainSeriesRenderer.nonInteractive)
- )
- return;
- if (this.runPrepend("positionCrosshairsAtPointer", arguments)) return;
- var chart = currentPanel.chart;
- var rect = this.container.getBoundingClientRect();
- this.top = rect.top;
- this.left = rect.left;
- this.right = this.left + this.width;
- this.bottom = this.top + this.height;
- this.cy = this.crossYActualPos = this.backOutY(CIQ.ChartEngine.crosshairY);
- this.cx = this.backOutX(CIQ.ChartEngine.crosshairX);
- var crosshairTick = (this.crosshairTick = this.tickFromPixel(this.cx, chart));
- var position = this.pixelFromTick(crosshairTick, chart) - 1;
- if (this.controls.crossX) this.controls.crossX.style.left = position + "px";
- if (position >= currentPanel.right || position <= currentPanel.left) {
- this.undisplayCrosshairs();
- return;
- }
- var chField =
- currentPanel.name == "chart"
- ? this.preferences.horizontalCrosshairField
- : currentPanel.horizontalCrosshairField;
- var dataSet = chart.dataSet;
- if (
- chField &&
- dataSet &&
- crosshairTick < dataSet.length &&
- crosshairTick > -1
- ) {
- this.crossYActualPos = this.pixelFromPrice(
- dataSet[crosshairTick][chField],
- currentPanel
- );
- }
- if (this.controls.crossY)
- this.controls.crossY.style.top = this.crossYActualPos + "px";
- this.runAppend("positionCrosshairsAtPointer", arguments);
- };
- /**
- * INJECTABLE
- *
- * Internal function that makes the crosshairs visible based on where the user's mouse pointer is
- * located. This function should not be called directly.
- *
- * Crosshairs are visible if enabled, unless a drawing tool is active, in which case they are
- * displayed automatically regardless of state.
- *
- * When the user's mouse moves out of the chart or over a modal, the crosshairs are
- * automatically made invisible using
- * {@link CIQ.ChartEngine.AdvancedInjectable#undisplayCrosshairs}.
- *
- * To temporarily show or hide enabled crosshairs, use {@link CIQ.ChartEngine#showCrosshairs}
- * and {@link CIQ.ChartEngine#hideCrosshairs}, respectively.
- *
- * **Note:** If the z-index of the crosshairs is set higher than the z-index of the subholder
- * element, the crosshairs cannot be controlled by the chart engine.
- *
- * @alias doDisplayCrosshairs
- * @memberof CIQ.ChartEngine.AdvancedInjectable#
- * @since 5.0.0 No longer allows the crosshairs to be enabled if the mouse pointer is outside the
- * chart.
- */
- CIQ.ChartEngine.prototype.doDisplayCrosshairs = function () {
- if (this.runPrepend("doDisplayCrosshairs", arguments)) return;
- if (this.displayInitialized) {
- var floatCanvas = this.floatCanvas;
- var drawingTool = this.currentVectorParameters.vectorType;
- if (!this.layout.crosshair && (drawingTool === "" || !drawingTool)) {
- this.undisplayCrosshairs();
- } else if (
- CIQ.Drawing &&
- CIQ.Drawing[drawingTool] &&
- new CIQ.Drawing[drawingTool]().dragToDraw
- ) {
- this.undisplayCrosshairs();
- } else if (
- this.overXAxis ||
- this.overYAxis ||
- (!this.insideChart && !this.grabbingScreen)
- ) {
- this.undisplayCrosshairs();
- } else if (this.openDialog !== "") {
- this.undisplayCrosshairs();
- } else {
- var controls = this.controls,
- crossX = controls.crossX,
- crossY = controls.crossY;
- if (crossX && crossX.style.display !== "") {
- crossX.style.display = "";
- if (crossY) crossY.style.display = "";
- if (this.magnetizedPrice && drawingTool) {
- this.container.classList.remove("stx-crosshair-on");
- this.chart.tempCanvas.style.display = "block";
- } else {
- this.container.classList.add("stx-crosshair-on");
- }
- }
- if (controls.floatDate && !this.chart.xAxis.noDraw) {
- controls.floatDate.style.visibility = "";
- if (this.currentPanel) this.updateFloatHRLabel(this.currentPanel);
- }
- if (floatCanvas) {
- if (floatCanvas.style.display == "none")
- CIQ.clearCanvas(floatCanvas, this);
- floatCanvas.style.display = "block";
- }
- }
- }
- this.runAppend("doDisplayCrosshairs", arguments);
- };
-
- /**
- * INJECTABLE
- *
- * Internal function that makes the crosshairs invisible when the user mouses out of the chart or
- * over a chart control. This function should not be called directly.
- *
- * See {@link CIQ.ChartEngine.AdvancedInjectable#doDisplayCrosshairs} for more details.
- *
- * @alias undisplayCrosshairs
- * @memberof CIQ.ChartEngine.AdvancedInjectable#
- */
- CIQ.ChartEngine.prototype.undisplayCrosshairs = function () {
- if (this.runPrepend("undisplayCrosshairs", arguments)) return;
- var controls = this.controls,
- crossX = controls.crossX,
- crossY = controls.crossY;
- if (crossX) {
- if (crossX.style.display != "none") {
- crossX.style.display = "none";
- if (crossY) crossY.style.display = "none";
- }
- }
- if (this.displayInitialized && controls.floatDate) {
- controls.floatDate.style.visibility = "hidden";
- }
- this.container.classList.remove("stx-crosshair-on");
- var floatCanvas = this.floatCanvas;
- if (
- floatCanvas &&
- floatCanvas.isDirty &&
- floatCanvas.style.display != "none"
- ) {
- CIQ.clearCanvas(floatCanvas, this);
- if (floatCanvas.style.display != "none") floatCanvas.style.display = "none";
- }
- if (
- !this.activeDrawing &&
- !this.repositioningDrawing &&
- !this.editingAnnotation
- ) {
- var tempCanvas = this.chart.tempCanvas;
- if (tempCanvas && tempCanvas.style.display != "none")
- tempCanvas.style.display = "none";
- }
- this.runAppend("undisplayCrosshairs", arguments);
- };
-
- /**
- * Hides enabled crosshairs.
- *
- * Usually called as part of a custom drawing or overlay to prevent the crosshairs from displaying
- * together with the custom rendering.
- *
- * See CIQ.ChartEngine.layout[\`crosshair\`]
- * to enable/disable the crosshairs.
- *
- * @memberof CIQ.ChartEngine
- */
- CIQ.ChartEngine.prototype.hideCrosshairs = function () {
- this.displayCrosshairs = false;
- };
-
- /**
- * Re-displays crosshairs hidden by {@link CIQ.ChartEngine#hideCrosshairs}.
- *
- * @memberof CIQ.ChartEngine
- */
- CIQ.ChartEngine.prototype.showCrosshairs = function () {
- this.displayCrosshairs = true;
- };
-
- };
-
-
- let __js_core_engine_data_ = (_exports) => {
-
-
- var CIQ = _exports.CIQ,
- timezoneJS = _exports.timezoneJS;
-
- /**
- * Loads a chart for a particular instrument from the data passed in, or fetches new data from the {@link quotefeed}; if one attached.
- *
- * Replaces {@link CIQ.ChartEngine#newChart}.
- *
- * Note that before using this method, you must first instantiate the chart engine (once only) and assign it to a DOM container using [new CIQ.ChartEngine({container: document.querySelector(".chartContainer")});]{@link CIQ.ChartEngine}
- * Once a chart engine is instantiated, this is the only method that should be called every time a new chart needs to be drawn for a different instrument.
- * There is no need to destroy the chart, recreate the engine, or explicitly change the data using any other methods.
- *
- * Charts default to `1 day` periodicity **unless a different periodicity is set** in this call or by using {@link CIQ.ChartEngine#setPeriodicity} prior to this call. You data must always match the chart periodicity!!
- *
- * @param {string|object} symbol A symbol string, equation or object representing the primary instrument for the chart. **This is a mandatory field and must contain at least one character for the chart to display data, even is not using a primary instrument.**
- *
After the chart is initialized with the new data, it will contain both a symbol string (stxx.chart.symbol) and a symbol object (stxx.chart.symbolObject).
- *
You can send anything you want in the symbol object, but you must always include at least a 'symbol' element.
- *
Both these variables will be available for use wherever the {@link CIQ.ChartEngine.Chart} object is present. For example, if using a {@link quotefeed} for gathering data, `params.stx.chart.symbolObject` will contain your symbol object.
- *
To allow equations to be used on a chart, the {@link CIQ.ChartEngine#allowEquations} parameter must be set to `true` and the equation needs to be preceded by an equals sign (=) in order for it to be parsed as an equation.
- *
See {@link CIQ.formatEquation} and {@link CIQ.computeEquationChart} for more details on allowed equations syntax.
- * @param {Object|Array} [parameters] Data & configuration settings to initialize the chart.
- *
The masterData array may be provided as the second argument assuming no other parameters need to be specified.
- * @param {Array} [parameters.masterData] An array of [properly formatted objects]{@tutorial InputDataFormat} to create a chart.
- *
Each element should at a minimum contain a "Close" or "Value" field (capitalized) and a 'Date' or 'DT' field.
- *
If the charting engine has been configured to use a [QuoteFeed]{@link CIQ.ChartEngine#attachQuoteFeed}
- * then masterData does not need to be passed in, and the quote feed will be used instead.
- * @param {CIQ.ChartEngine.Chart} [parameters.chart] Which chart to load. Defaults to this.chart.
- * @param {CIQ.ChartEngine~RangeParameters} [parameters.range] Default range to be used upon initial rendering. If both `range` and `span` parameters are passed in, range takes precedence. If periodicity is not set, the range will be displayed at the most optimal periodicity. See {@link CIQ.ChartEngine#setRange} for complete list of parameters this object will accept.
- * @param {CIQ.ChartEngine~SpanParameters} [parameters.span] Default span to display upon initial rendering. If both `range` and `span` parameters are passed in, range takes precedence. If periodicity is not set, the span will be displayed at the most optimal periodicity. See {@link CIQ.ChartEngine#setSpan} for complete list of parameters this object will accept.
- * @param {CIQ.ChartEngine~PeriodicityParameters} [parameters.periodicity] Periodicity to be used upon initial rendering. See {@link CIQ.ChartEngine#setPeriodicity} for complete list of parameters this object will accept. If no periodicity has been set, it will default to `1 day`.
- * @param {boolean} [parameters.stretchToFillScreen] Increase the candleWidth to fill the left-side gap created by a small dataSet. Respects CIQ.ChartEngine.preferences.whitespace. Ignored when params `span` or `range` are used. See {@link CIQ.ChartEngine#fillScreen}
- * @param {Function} [callback] Called when loadChart is complete. See {@tutorial Adding additional content on chart} for a tutorial on how to use this callback function.
- * @memberof CIQ.ChartEngine
- * @example
- * On a fetch call, if your quote server sends and receives string dates loaded in the 'Date' field,
- * you can convert the provided start and end dates back to strings using {@link CIQ.yyyymmddhhmmssmmm}
- * Example:
- * ```
- * var strStart = CIQ.yyyymmddhhmmssmmm(startDate);
- * var strEnd = CIQ.yyyymmddhhmmssmmm(endDate);
- * ```
- * These dates will be in the same time zone you sent them in. So they will match your quote feed.
- *
- * For more details on how time zones work in the chart see the {@tutorial Dates and Timezones} tutorial.
- *
- * **See {@link CIQ.timeZoneMap} to review a list of all chatIQ supported timezones and instructions on how to add more!**
- *
- * @param {string} dataZone A ChartIQ supported time zone. This should represent the time zone that the master data comes from, or set to 'null' if your dates are already time zone aware.
- * @param {string} displayZone A ChartIQ supported time zone. This should represent the time zone that the user wishes displayed, or set to null to use the browser time zone.
- * @memberof CIQ.ChartEngine
- * @since 5.2 Also used to convert daily, weekly and monthly periodicities.
- * @example
- * //The raw data received the chart is in Greenwich Mean Time, but we want to display in Amsterdam time.
- * stxx.setTimeZone("UTC", "Europe/Amsterdam")
- *
- *
- */
- CIQ.ChartEngine.prototype.setTimeZone = function (dataZone, displayZone) {
- if (!timezoneJS.Date) {
- this.timeZoneOffset = 0;
- return;
- }
-
- var now = new Date();
- var myTimeZoneOffset = now.getTimezoneOffset();
- var dataTimeZoneOffset = myTimeZoneOffset;
- var displayTimeZoneOffset = myTimeZoneOffset;
- if (dataZone) this.dataZone = dataZone;
- if (this.dataZone)
- dataTimeZoneOffset = new timezoneJS.Date(
- now,
- this.dataZone
- ).getTimezoneOffset();
- if (displayZone) this.displayZone = displayZone;
- if (this.displayZone)
- displayTimeZoneOffset = new timezoneJS.Date(
- now,
- this.displayZone
- ).getTimezoneOffset();
- this.timeZoneOffset =
- dataTimeZoneOffset -
- myTimeZoneOffset -
- (displayTimeZoneOffset - myTimeZoneOffset);
- for (var chartName in this.charts) {
- var chart = this.charts[chartName];
- this.setDisplayDates(chart.masterData);
- }
- this.preferences.timeZone = displayZone;
- this.changeOccurred("preferences");
- this.createDataSet();
- };
-
- /**
- * INJECTABLE
- *
- * Use this method to add new `OHLC` bars to the end of the chart, insert new bars into the middle of the chart, replace existing bars, delete bars, or stream individual `LAST SALE` data tick by tick as they are received from a streaming feed.
- *
- * **The following rules apply when adding or updating full [`OHLC`]{@tutorial InputDataFormat} bars:**
- *
- * - Follow proper OHLC format as outlined on the [OHLC format tutorial]{@tutorial InputDataFormat}.
- * - If a bar is not present it will be added, if it is present it will be updated so the OHLC and volume integrity is preserved. If `allowReplaceOHL` is not set, the 'Open' is preserved from the existing candle; new 'High' and 'Low' values are calculated, and the 'Close' and 'Volume' values are replaced with the new ones.
- * - Although gaps can be present, dates in the appendQuotes array **must maintain the correct periodicity and order** (older to newer) to prevent out of sequence bars.
- * - If set, gaps will be filled past the currently existing bar. No gaps will be filled when inserting bars in between existing data.
- *
- * **The following rules apply when streaming individual `LAST SALE` data, tick by tick, as they are received from a streaming feed:**
- *
- * - Follow proper LAST SALE format as outlined on the parameters section under the `appendQuotes` field.
- * - This method is designed to update the chart while maintaining the existing periodicity, finding and augmenting an existing bar for an instrument or creating new bars as needed.
- * - It is important to note that a market iterator will be used to find the proper bar to update, and if no bar is found on that date, one will be created even in the past; so always be sure your historical data follows the rules of the market definitions when setting the dates for each bar. Remember that by default, weeks start on Sunday unless a market definition exists to indicate Sunday is not a market day, in which case the next market day will be used as the beginning of the week. Instructions to set a market for the chart can be found here: {@link CIQ.Market}
- * - When in 'tick' interval, each trade will be added to a new bar and no aggregation to previous bars will be done.
- *
- * **The following rules apply when updating `BID` and `ASK` prices separately from the primary series.**
- *
- * - Bid, Ask and Volume are reserved for the primary series only.
- * - The reasoning is that if your initial data sends a Bid-Ask together with the 'Close' (Last), your updates will as well; which is usually the norm.
- * - But if your feed sends updates for Bid and Asks separately than for the 'Last' price, then you must add this additional data as you would do any other secondary series.
- *
- * > Assuming you have this data pre-loaded on your chart already containing Bid and Ask prices:
- * > ```
- * > [
- * > {
- * > "DT": "2019-11-19T18:17:29.000Z",
- * > "Close": 266.12,
- * > "Volume": 300,
- * > "Bid": 266.1,
- * > "Ask": 266.12,
- * > },
- * > {
- * > "DT": "2019-11-19T18:17:29.000Z",
- * > "Close": 266.12,
- * > "Volume": 300,
- * > "Bid": 266.1,
- * > "Ask": 266.12,
- * > }
- * > ]
- * > ```
- * > And have added this series to display the pre-loaded Bid prices:
- * > ```
- * > stxx.addSeries("Bid", {color: "green", loadData: false, shareYAxis: true, step:true});
- * > ```
- * > Use:
- * > ```
- * > stxx.updateChartData({Close:90}, null, { useAsLastSale: true, secondarySeries: "Bid" });
- * > ```
- * > or
- * > ```
- * > stxx.updateChartData({Last:90}, null, {secondarySeries: "Bid" });
- * > ```
- * > to update the bid prices.
- *
- * **Performance:**
- *
- * - To maintain system performance you can throttle inbound ticks. See {@link CIQ.ChartEngine#streamParameters } and [Streaming tutorial]{@tutorial DataIntegrationStreaming} for more details.
- * - It is important to note that although the data will always be added to masterData, `createDataSet()` and `draw()` will **not** be called if data is received quicker than the throttle (governor) wait periods. As such, you will not see any changes until the throttle wait periods are met.
- * - **Please adjust default settings if your implementation requires immediate updates.**
- *
- * **Additional Notes:**
- *
- * - **It is crucial that you ensure the date/time of the records being loaded are in line with your `masterData` and `dataZone`; and in the case of a last trade streaming, that your market definition will produce dates that will be in sync with the rest of your already loaded records.** See `DT` parameter for more details.
- * - This method is **not** intended to be used as a way to load initial chart data, or data changes triggered by periodicity changes.
- * - Do not stream current updates into the chart using this method if you have used [setSpan]{@link CIQ.ChartEngine#setSpan} or [setRange]{@link CIQ.ChartEngine#setRange} to enter 'historical mode'.
- * When in historical mode, forward pagination is based on the date of the last loaded bar, and streaming current updates will create a data gap.
- * To check if you are in historical mode evaluate {@link CIQ.ChartEngine#isHistoricalModeSet}
- *
- * See the [Data Integration]{@tutorial DataIntegrationOverview} tutorial for more detail on how to load initial data.
- *
- * See the [Streaming]{@tutorial DataIntegrationStreaming} tutorial for more the details.
- *
- * @param {array|object} appendQuotes **OHLC format requirements**
- * An **array** of properly formatted OHLC quote object(s). [See OHLC Data Format]{@tutorial InputDataFormat}.
- * Items in this array *must* be ordered from earliest to latest date.
- * As a convenience, for more generic data updates, instead of an entire OHLC record, a field of `Value` can be used as an alternative to `Close`.
- * Examples:
- * ```
- * {
- * DT: stxx.masterData[i].DT,
- * Value: 148
- * }
- * ```
- * ```
- * {
- * Date: '12/31/2011',
- * Value: 148
- * }
- * ```
- *
- * **LAST SALE format requirements**
- * An **object** with the following elements:
- * @param {number} [appendQuotes.Last] Last sale price
- * @param {number} [appendQuotes.Volume] Trade volume (**used on primary series only**)
- * @param {number} [appendQuotes.Bid] Bid price (**used on primary series only**)
- * @param {number} [appendQuotes.Ask] Offer/Ask price (**used on primary series only**)
- * @param {array} [appendQuotes.BidL2] Level 2 Bid, expressed as an array of [price,size,obj] pairs.
For example, BidL2: [[10.05, 15, {...}],[10.06, 10, {...}],...].
- * `obj` is an optional object which can contain whatever you wish. It will be conveyed all the way into the marketdepth chart and can be displayed by using the 'headsUp' method of displaying crosshair data.
- * @param {array} [appendQuotes.AskL2] Level 2 Offer/Ask expressed as an array of [price,size,obj] pairs.
For example, AskL2: [[11.05, 12, {...}],[11.06, 8, {...}],...].
- * `obj` is an optional object which can contain whatever you wish. It will be conveyed all the way into the marketdepth chart and can be displayed by using the 'headsUp' method of displaying crosshair data.
- * @param {number} [appendQuotes.DT] Date of trade. It must be a java script date [new Date()]. If omitted, defaults to "right now".
- *
**Last sale format DOES NOT ALLOW THE USE OF A `Date` FIELD**.
- *
If you are using the 'Date' string field with a `dataZone` for your historical data and wish to also use it for streaming last sale updates,
- * you must instead submit a properly formatted OHLC array with `useAsLastSale` set to `true`. Like this:
- * ```
- * stxx.updateChartData(
- * [
- * {"Date":"2015-04-16 16:00","Close":152.11,"Volume":4505569}
- * ],
- * null,
- * {useAsLastSale:true}
- * );
- * ```
- * @param {CIQ.ChartEngine.Chart} [chart] The chart to append the quotes. Defaults to the default chart.
- * @param {object} [params] Parameters to dictate behavior
- * @param {boolean} [params.noCreateDataSet] If true then do not create the data set automatically, just add the data to the masterData
- * @param {boolean} [params.noCleanupDates] If true then do not clean up the dates using {@link CIQ.ChartEngine.doCleanupDates}. Usually set if dates were already cleaned up.
- * @param {boolean} [params.allowReplaceOHL] Set to true to bypass internal logic that maintains OHL so they are instead replaced with the new data instead of updated.
- * @param {boolean} [params.bypassGovernor] If true then dataSet will be immediately updated regardless of {@link CIQ.ChartEngine#streamParameters}. Not applicable if `noCreateDataSet` is true.
- * @param {boolean} [params.fillGaps] If true and {@link CIQ.ChartEngine#cleanupGaps} is also set, {@link CIQ.ChartEngine#doCleanupGaps} will be called to fill gaps for any newly added bars past the currently existing bar. It will not fill gaps for bars added to the middle of the masterData, or created by deleting a bar.
Reminder: `tick` does not fill any gaps as it is not a predictable interval.
- * @param {string} [params.secondarySeries] Set to the name of the element (valid comparison symbol, for example) to load data as a secondary series. When left out, the data will be automatically added to the primary series.
**Note:** You should never set `secondarySeries` to the primary symbol. If you are unsure of what the current primary series is, you can always query the chart engine by checking `stxx.chart.symbol`.
- * @param {boolean} [params.deleteItems] Set to true to completely delete the masterData records matching the dates in appendQuotes.
- * @param {boolean} [params.useAsLastSale] Set to true if not using a 'last sale' formatted object in `appendQuotes`.
- * This option is available in cases when a feed may always return OHLC formatted objects or a 'Close' field instead of a 'Last' field,
- * even for last sale streaming updates.
- * By definition a 'last sale' can only be a single record indicating the very 'last' sale price.
- * As such, even if multiple records are sent in the `appendQuotes` array when this flag is enabled,
- * only the last record's data will be used. Specifically the 'Close' and 'Volume' fields will be streamed.
- * @param {boolean} [params.useAsLastSale.aggregatedVolume] If your last sale updates send current volume for the bar instead of just the trade volume, set this parameter to 'true' in the `params.useAsLastSale` object. The sent in volume will be used as is instead of being added to the existing bar's volume. Not applicable when loading data for a secondary series.
- * @memberof CIQ.ChartEngine
- * @example
- * // this example will stream the last price on to the appropriate bar and add 90 to the bar's volume.
- * stxx.updateChartData(
- * {
- * Last: 50.94,
- * Volume: 90
- * }
- * );
- * @example
- * // this example will stream the last price on to the appropriate bar and set the volume for that bar to 90.
- * stxx.updateChartData(
- * {
- * Last: 50.94,
- * Volume: 90
- * },
- * null,
- * {useAsLastSale: {aggregatedVolume:true}}
- * );
- * @example
- * // this example will stream the last price to the appropriate bar **for a secondary series**.
- * stxx.updateChartData(
- * {
- * Last: 50.94
- * },
- * null,
- * {secondarySeries:secondarySymbol}
- * );
- * @example
- * // this example will add or replace a complete bar.
- * stxx.updateChartData(
- * [
- * {"Date":"2015-04-16 16:00","Open":152.13,"High":152.19,"Low":152.08,"Close":152.11,"Volume":4505569},
- * {"Date":"2015-04-17 09:30","Open":151.76,"High":151.83,"Low":151.65,"Close":151.79,"Volume":2799990},
- * {"Date":"2015-04-17 09:35","Open":151.79,"High":151.8,"Low":151.6,"Close":151.75,"Volume":1817706}
- * ]
- * );
- * @example
- * // this example will add or replace a complete bar.
- * stxx.updateChartData(
- * [
- * {"Date":"2015-04-16 16:00","Value":152.13},
- * ]
- * );
- * @since
- * - 5.1.0 New function replacing and enhancing legacy method `appendMasterData`.
- * - 5.1.0 Added ability to delete or insert items anywhere in the masterData. `deleteItems` parameter added.
- * - 5.2.0 Added `overwrite` parameter.
- * - 5.2.0 For main series data, if Close=null is set, and not streaming, then Open, High, Low and Volume also set to null.
- * - 5.2.0 For main series data, if Volume=0/null is set, and not streaming, then Volume is reset to 0.
- * - 5.2.0 Added `params.noCleanupDates`; `params.fillGaps` applicable now for secondary series as well.
- * - 6.0.0 Removed `overwrite` parameter.
- * - 6.1.0 Added BidL2 and AskL2 to `appendQuotes` object.
- * - 6.3.0 `appendQuotes` can now take `Value` instead of `Close`.
- * - 6.3.0 Added `obj` to BidL2 and AskL2 array elements to allow vendor specific data to be displayed on the chart tooltip.
- * - 7.2.0 Method now rolls up ticks if period is greater than 1.
- */
- CIQ.ChartEngine.prototype.updateChartData = function (
- appendQuotes,
- chart,
- params
- ) {
- if (!params) params = {};
- if (!chart) chart = this.chart;
-
- var lastSale = false,
- aggregatedVolume = false,
- masterData = chart.masterData,
- layout = this.layout,
- dataZone = this.dataZone;
- var self = this,
- secondary = params.secondarySeries,
- field,
- symbol;
- var isValidNumber = CIQ.isValidNumber;
-
- // If we are not a tick interval, we want to adjust the DT property of the appendQuotes so it matches the periodicity/interval of the existing chart data.
- function adjustDatesToInterval() {
- if (!CIQ.Market || !chart.market) return;
- // On intraday intervals we use a 24 hour market because we don't want our bars to artificially stop
- // at the end of a market session. If we get extended hours, or bad ticks we still
- // want to print them on the chart. Trust the data.
- var marketDef = {
- market_tz: CIQ.getFromNS(chart, "market.market_def.market_tz", null)
- };
- var mktInterval = layout.interval;
-
- if (mktInterval == "month" || mktInterval == "week") {
- // if we are rolling day bars into week or month we have to iterate day by day to find the right bar.
- if (!self.dontRoll) mktInterval = "day";
- // on week and month we need to know when the week/month starts to find the right day for the candles.
- marketDef = self.chart.market.market_def;
- }
-
- var theMarket = new CIQ.Market(marketDef);
- var iter_parms = {
- begin:
- masterData && masterData.length
- ? masterData[masterData.length - 1].DT
- : appendQuotes.DT,
- interval: mktInterval,
- periodicity: 1,
- timeUnit: layout.timeUnit
- };
-
- var iter = theMarket.newIterator(iter_parms);
- var next = iter.next();
- var max, actualTime;
- if (!masterData) {
- // there are some use cases where you might prefer to stream data onto masterData without using a quotefeed or loading data first.
- appendQuotes.DT = new Date(+iter.previous());
- } else if (appendQuotes.DT < next) {
- // update current tick or some tick in the past.
- max = 0; // safety catch so we don't go on forever.
- var previous = iter.previous();
- actualTime = appendQuotes.DT;
- params.appending = true;
- while (actualTime < previous && max < 1000) {
- params.appending = false;
- previous = iter.previous();
- max++;
- }
- appendQuotes.DT = previous;
- params.updating = !params.appending;
- } else if (appendQuotes.DT >= next) {
- // create new tick. If the date matches, that's it, otherwise fast forward to find the right bar to add.
- max = 0; // safety catch so we don't go on forever.
- actualTime = appendQuotes.DT;
- while (actualTime > next && max < 1000) {
- appendQuotes.DT = next;
- next = iter.next();
- max++;
- }
- params.appending = true;
- }
- }
-
- // Takes the Last Sale data from the appendQuote and converts it to OHLC data
- function formatFromLastSaleData() {
- // self is last sale streaming so format accordingly
- lastSale = true;
-
- if (params.useAsLastSale && params.useAsLastSale.aggregatedVolume)
- aggregatedVolume = true;
-
- if (appendQuotes.constructor === Array) {
- // is streaming an array of OHLC, do some clean up to extract last and volume
- var lastBar = appendQuotes[appendQuotes.length - 1];
- appendQuotes = {};
-
- // doCleanupDates will make sure this has a valid 'DT' field in the right timeZone,
- // no need to check or convert from 'Date'
- appendQuotes.DT = lastBar.DT;
-
- appendQuotes.Close = lastBar.Close;
- appendQuotes.Volume = lastBar.Volume;
- } else if (appendQuotes.Last) {
- appendQuotes.Close = appendQuotes.Last;
- delete appendQuotes.Last;
- }
-
- if (
- appendQuotes.DT &&
- Object.prototype.toString.call(appendQuotes.DT) != "[object Date]"
- )
- appendQuotes.DT = new Date(appendQuotes.DT); // epoch or ISO string
- if (!appendQuotes.DT || appendQuotes.DT == "Invalid Date") {
- // if no date is sent in, use the current time and adjust to the dataZone
- appendQuotes.DT = new Date();
- }
-
- // find the right candle
- if (layout.interval != "tick") {
- adjustDatesToInterval();
- }
-
- appendQuotes.Open = appendQuotes.Close;
- appendQuotes.High = appendQuotes.Close;
- appendQuotes.Low = appendQuotes.Close;
- }
-
- // Fills the gaps from the most recent master data record to the new data
- function fillGapsFromMasterDataHead() {
- var lastRecordForThis = null;
- var fg = 0; // this is used to store the index of the first record in appendQuotes we should be using to fill gaps.
- // we'll adjust this below by looking for the starting point from masterData
- if (masterData.length) {
- lastRecordForThis = self.getFirstLastDataRecord(
- masterData,
- secondary || chart.defaultPlotField,
- true
- );
- if (lastRecordForThis) {
- if (appendQuotes[appendQuotes.length - 1].DT <= lastRecordForThis.DT)
- return; // no gap to fill
- for (; fg < appendQuotes.length; fg++) {
- if (+appendQuotes[fg].DT == +lastRecordForThis.DT) {
- // if the appendQuote is the same as the lastRecordForThis, check to see which is the "correct" record
- if (
- self.getFirstLastDataRecord(
- [appendQuotes[fg]],
- secondary || chart.defaultPlotField
- )
- )
- lastRecordForThis = null; // use appendQuote record
- break;
- } else if (appendQuotes[fg].DT > lastRecordForThis.DT) break;
- }
- }
- }
- // now fg represents the index of the first element in appendQuotes which appears after the last current element for that security.
- var gapQuotes = appendQuotes.slice(fg);
- if (lastRecordForThis)
- gapQuotes.unshift(
- secondary ? lastRecordForThis[secondary] : lastRecordForThis
- ); // add previous bar so we can close gaps
- gapQuotes = self.doCleanupGaps(gapQuotes, chart);
- if (lastRecordForThis) gapQuotes.shift(); // remove previous bar
- appendQuotes = appendQuotes.slice(0, fg).concat(gapQuotes);
- }
-
- // Deletes an item from masterData at index i and date dt
- function deleteThisItem(i, dt) {
- var replace;
- if (secondary) {
- delete masterData[i][secondary];
- if (self.cleanupGaps) {
- replace = { DT: dt, Close: null };
- if (
- self.cleanupGaps != "gap" &&
- masterData[i - 1] &&
- masterData[i - 1][secondary]
- ) {
- replace.Close = masterData[i - 1][secondary].Close;
- replace.High = replace.Low = replace.Open = replace.Close;
- replace.Volume = 0;
- }
- masterData[i][secondary] = replace;
- }
- } else {
- var spliced = masterData.splice(i, 1)[0]; //deleting from masterData here, but will reinsert if find any series data
- replace = { DT: spliced.DT, Close: null, needed: false };
- for (field in chart.series) {
- symbol = chart.series[field].parameters.symbolObject.symbol;
- if (typeof spliced[symbol] != "undefined") {
- replace[symbol] = spliced[symbol];
- delete replace.needed;
- }
- }
- if (self.cleanupGaps && self.cleanupGaps != "gap") {
- delete replace.needed;
- if (self.cleanupGaps != "gap" && masterData[i - 1]) {
- replace.Close = masterData[i - 1].Close;
- replace.High = replace.Low = replace.Open = replace.Close;
- replace.Volume = 0;
- }
- }
- if (replace.needed !== false) {
- masterData.splice(i, 0, replace);
- self.setDisplayDate(replace);
- }
- }
- }
-
- // Takes masterData at index i and merges it into a quote q
- function mergeMasterDataIntoNewData(i, q) {
- // If we're replacing the last bar then we want to save any series and study data, otherwise comparisons will [briefly] disappear during refreshes
- //Preserve any relevant data from prior fetched quote for this bar.
- //Here we are assuming that the data being appended to masterData is a data update, perhaps from only one exchange, while
- //the existing masterData is a consolidated quote. We trust the quote we had in masterData to have the more accurate
- //volume and open, and use the high/low from there in combination with the updated data's to determine the daily high/low.
- var master = masterData[i];
- if (secondary) master = master[secondary] || {};
-
- if (q.Close === null) {
- if (master.Open !== undefined) q.Open = null;
- if (master.High !== undefined) q.High = null;
- if (master.Low !== undefined) q.Low = null;
- if (master.Volume !== undefined) q.Volume = null;
- // This code will set the OHLC data for carry gap filling if applicable,
- // but it's disabled because if a Close:null is sent in, then just use it.
- // I suppose if a gap is really to be filled in, the record should be deleted.
- /*if(this.cleanupGaps && this.cleanupGaps!="gap" && masterData[i-1]){
- if(!secondary || masterData[i-1][secondary]){
- q.Close=secondary?masterData[i-1][secondary].Close:masterData[i-1].Close;
- q.High=q.Low=q.Open=q.Close;
- q.Volume=0;
- }
- }*/
- } else {
- if (lastSale) {
- if (q.Volume) {
- q.Volume = parseInt(q.Volume, 10);
- }
- if (!aggregatedVolume) q.Volume += master.Volume;
- } else {
- if (!isValidNumber(q.Volume) && master.Volume) {
- q.Volume = master.Volume;
- }
- }
- if (!params.allowReplaceOHL) {
- if (isValidNumber(master.Open)) {
- q.Open = master.Open;
- }
- if (isValidNumber(master.High) && isValidNumber(q.High)) {
- if (master.High > q.High) q.High = master.High;
- }
- if (isValidNumber(master.Low) && isValidNumber(q.Low)) {
- if (master.Low < q.Low) q.Low = master.Low;
- }
- }
- // if new data is invalid, revert to old data
- ["Close", "Open", "High", "Low", "Bid", "Ask"].forEach(function (field) {
- if (!isValidNumber(q[field])) q[field] = master[field];
- });
-
- for (field in chart.series) {
- symbol = chart.series[field].parameters.symbolObject.symbol;
- if (
- typeof q[symbol] == "undefined" &&
- typeof master[symbol] != "undefined"
- )
- q[symbol] = master[symbol];
- }
- }
- }
-
- if (!params.noCleanupDates)
- this.doCleanupDates(appendQuotes, layout.interval);
-
- if (
- params.useAsLastSale ||
- (appendQuotes.constructor == Object &&
- (appendQuotes.Last || appendQuotes.Last === 0))
- ) {
- formatFromLastSaleData();
- }
-
- if (appendQuotes && appendQuotes.constructor == Object)
- appendQuotes = [appendQuotes]; // When developer mistakenly sends an object instead of an array of objects
- if (!appendQuotes || !appendQuotes.length) return;
- if (this.runPrepend("appendMasterData", [appendQuotes, chart, params]))
- return;
- if (this.runPrepend("updateChartData", [appendQuotes, chart, params])) return;
-
- if (!masterData) masterData = [];
-
- var i = masterData.length - 1,
- placedFirstQuote = false;
-
- // we only fill from the end of the current data, not before
- if (params.fillGaps) fillGapsFromMasterDataHead();
- if (!appendQuotes.length) return; // can happen within fillGapsFromMasterDataHead
-
- for (var j = 0; j < appendQuotes.length; j++) {
- var quote = appendQuotes[j];
- var dt = quote.DT,
- date = quote.Date;
- if (dt && Object.prototype.toString.call(dt) != "[object Date]")
- quote.DT = dt = new Date(dt); // if already a date object; nothing to do
- if (dt) {
- if (!date || date.length != 17)
- quote.Date = CIQ.yyyymmddhhmmssmmm(quote.DT);
- }
- if (!dt) dt = quote.DT = CIQ.strToDateTime(date);
-
- // If Value provided, it has special meaning if Close not provided (it's the Close)
- if (!isValidNumber(quote.Close) && isValidNumber(quote.Value)) {
- quote.Close = quote.Value;
- }
-
- while (i >= 0 && i < masterData.length) {
- var dt2 = masterData[i].DT;
- if (!dt2) dt2 = CIQ.strToDateTime(masterData[i].Date);
- if (dt2.getTime() <= dt.getTime()) {
- placedFirstQuote = true;
- var plusOne = 0; // If time is the same then replace last bar
- if (dt2.getTime() < dt.getTime()) {
- if (i < masterData.length - 1) {
- var dtf =
- masterData[i + 1].DT || CIQ.strToDateTime(masterData[i + 1].Date);
- if (dtf.getTime() <= dt.getTime()) {
- i++;
- continue;
- }
- }
- plusOne = 1; // Otherwise append bar
- }
- if (params.deleteItems) {
- if (!plusOne) deleteThisItem(i, dt);
- break;
- } else {
- // Under tick mode, always append bars. If animating, append on the first loop and replace on subsequent loops
- if (layout.interval == "tick" && params.firstLoop !== false)
- plusOne = 1;
- if (!plusOne) mergeMasterDataIntoNewData(i, quote);
-
- // Here we rectify any missing/malformatted data and set any new high/low
- // If we don't set this here, the study calculations will fail
- if (isValidNumber(quote.Close)) {
- if (!isValidNumber(quote.Open)) quote.Open = quote.Close;
-
- var high = Math.max(quote.Open, quote.Close),
- low = Math.min(quote.Open, quote.Close);
- if (!isValidNumber(quote.High) || quote.High < high)
- quote.High = high;
- if (!isValidNumber(quote.Low) || quote.Low > low) quote.Low = low;
- }
- if (quote.Volume && !isValidNumber(quote.Volume))
- quote.Volume = parseInt(quote.Volume, 10);
- i += plusOne;
-
- // Insert into masterData here
- if (secondary) {
- if (appendQuotes.length - j < 50) {
- // only check last 50 records
- this.updateCurrentMarketData(quote, chart, secondary, {
- fromTrade: true
- });
- }
- if (layout.interval != "tick" || quote.Close !== undefined) {
- if (plusOne) {
- masterData.splice(i, 0, { DT: quote.DT });
- this.setDisplayDate(masterData[i]);
- }
- masterData[i][secondary] = quote;
- }
- } else {
- if (appendQuotes.length - j < 50) {
- // only check last 50 records
- this.updateCurrentMarketData(quote, chart, null, {
- fromTrade: true
- });
- }
- if (layout.interval != "tick" || quote.Close !== undefined) {
- // inserting into masterData happens here
- masterData.splice(i, plusOne ? 0 : 1, quote);
- this.setDisplayDate(quote);
- }
- }
- }
- break;
- }
- i += placedFirstQuote ? 1 : -1;
- }
- if (i < 0) {
- // we have at least one point which needs to be prepended to masterData
- // this code will prepend the first of these points, then everything else will fall in line
- if (secondary) {
- this.updateCurrentMarketData(quote, chart, secondary, {
- fromTrade: true
- });
- if (layout.interval != "tick" || quote.Close !== undefined) {
- masterData.splice(0, 0, { DT: quote.DT });
- this.setDisplayDate(masterData[0]);
- masterData[0][secondary] = quote;
- }
- } else {
- this.updateCurrentMarketData(quote, chart, null, { fromTrade: true });
- if (layout.interval != "tick" || quote.Close !== undefined) {
- masterData.splice(0, 0, quote);
- this.setDisplayDate(quote);
- }
- }
- placedFirstQuote = true;
- i = 0;
- }
- }
- if (masterData.length) this.masterData = chart.masterData = masterData;
- if (this.maxMasterDataSize)
- masterData = chart.masterData = this.masterData = masterData.slice(
- -this.maxMasterDataSize
- );
-
- var series = secondary
- ? this.getSeries({ symbol: secondary, chart: chart })
- : [chart];
- for (var s = 0; s < series.length; s++) {
- var handle = series[s];
- if (!handle.endPoints.begin || handle.endPoints.begin > appendQuotes[0].DT)
- handle.endPoints.begin = appendQuotes[0].DT;
- if (
- !handle.endPoints.end ||
- handle.endPoints.end < appendQuotes[appendQuotes.length - 1].DT
- )
- handle.endPoints.end = appendQuotes[appendQuotes.length - 1].DT;
- var hField =
- (handle.parameters && handle.parameters.field) || chart.defaultPlotField;
- var lastQuote = this.getFirstLastDataRecord(appendQuotes, hField, true);
- if (lastQuote && (!handle.lastQuote || handle.lastQuote.DT <= lastQuote.DT))
- handle.lastQuote = lastQuote;
- if (secondary && params.deleteItems)
- handle.lastQuote = this.getFirstLastDataRecord(
- masterData,
- secondary,
- true
- )[secondary];
- }
- for (var pl in this.plugins) {
- var plugin = this.plugins[pl];
- if (plugin.display) {
- if (plugin.appendMasterData)
- plugin.appendMasterData(this, appendQuotes, chart);
- }
- }
- if (!this.masterData || !this.masterData.length) this.masterData = masterData;
-
- function dataSetAndDraw() {
- self.createDataSet(null, null, params);
- self.draw();
- self.updateChartAccessories();
- self.streamParameters.count = 0;
- self.streamParameters.timeout = -1;
- }
-
- if (!params.noCreateDataSet) {
- var sp = this.streamParameters;
- if (++sp.count > sp.maxTicks || params.bypassGovernor) {
- clearTimeout(sp.timeout);
- dataSetAndDraw();
- } else {
- if (sp.timeout == -1) {
- sp.timeout = setTimeout(dataSetAndDraw, sp.maxWait);
- }
- }
- }
- this.runAppend("appendMasterData", arguments);
- this.runAppend("updateChartData", arguments);
- };
-
- /**
- * INJECTABLE
- *
- * Loads or updates detailed current market information, such as L2 data, into the [chart.currentMarketData]{@link CIQ.ChartEngine.Chart#currentMarketData} object
- * or an equally laid out object for a secondary series (symbol), if one provided.
- *
- * **[draw()]{@link CIQ.ChartEngine#draw} must be called immediately after this method to see the updates.**
- *
- * A single ‘snapshot’ object per symbol is loaded and only the most current updates maintained.
- * This method is not intended to track historical or time-series information.
- *
- * This market ‘snapshot’ information can then be used to render specialty charts such as {@link CIQ.MarketDepth}, which is not a time series chart.
- * This data is also used to feed the Depth of Market indicator, [Trade History]{@link WebComponents.cq-tradehistory} and
- * [Order Book]{@link WebComponents.cq-orderbook} web components, part of the [Active Trader package](https://active-trader.demo.chartiq.com/).
- *
- * When using as part of a chart engine that also display a time-series chart, this method is automatically called with that same time-series data every time new data is load into the chart, thereby maintaing all charts in sync.
- * And only needs to be explicitly called when needing to update the L2 'snapshot' at a faster refresh rate than the rest of the time-series data, or if the time-series data does not provide this information.
- *
If using the {@link CIQ.MarketDepth} standalone, without a standard time series chart, you must call this method explicitly to load and refresh the data.
- *
- * Data Format:
- *
- * | Field | Required | Type | Description | Used for Active Trader | Used for TFC |
- * | ----------- | -------- | ---------------- | ---------------- | ---------------- | ---------------- |
- * | DT | Yes | A JavaScript Date() object | Timestamp for the data record | Yes | Yes |
- * | Bid | No | number | The current bid price | No | Yes |
- * | Ask | No | number | The current ask price | No | Yes |
- * | Last | No | number | The last (current) price.
If not present, the midpoint of the chart will be the average of the lowest bid and the highest ask.
Required on [Trade History](http://jsfiddle.net/chartiq/r2k80wcu) | Yes | Yes |
- * | BidSize | No | number | The bid size | No | No |
- * | AskSize | No | number | The ask size | No | No |
- * | LastSize | No | number | The last (current) price size.
Required on [Trade History](http://jsfiddle.net/chartiq/r2k80wcu) | Yes | No |
- * | LastTime | No | A JavaScript Date() object | Timestamp for the Last price provided.
Required on [Trade History](http://jsfiddle.net/chartiq/r2k80wcu) | Yes | No |
- * | BidL2 | No | array | Level 2 Bid, expressed as an array of [price,size] pairs.
For example, BidL2: [[10.05,15],[10.06,10],...]
Required on [Order Book](http://jsfiddle.net/chartiq/L30hna2s/) | Yes | No |
- * | AskL2 | No | array | Level 2 Ask, expressed as an array of [price,size] pairs.
For example, AskL2: [[10.05,15],[10.06,10],...]
Required on [Order Book](http://jsfiddle.net/chartiq/L30hna2s/) | Yes | No |
- *
- * Since not all of the data will need to be updated at the same time, this method allows you to send only the data that needs to be changed. Any values not provided will simply be skipped and not updated on the object.
- *
- * Example data format for a marketDepth chart:
- * ```
- * {
- * DT:new Date("2018-07-30T04:00:00.000Z"),
- * Last:100.2589,
- * BidL2:
- * [
- * [93.54,5],[93.65,2],[93.95,7],[95.36,2],
- * [95.97,9],[96.58,1], [96.68, 8], [96.98, 4],
- * [97.08, 5], [97.18, 5], [97.28, 3], [97.38, 5],
- * [97.48, 6], [97.69, 26], [98.29, 5], [98.39, 33],
- * [98.49, 13], [98.6, 42], [98.8, 13], [98.9, 1]
- * ],
- *
- * AskL2:
- * [
- * [101.22,226],[101.32,31],[101.42,13],[101.53,188],
- * [101.63,8],[101.73,5],[101.83,16],[101.93,130],
- * [102.03,9],[102.13,122],[102.23,5],[102.33,5],
- * [102.43,7],[102.54,9],[102.84,3],[102.94,92],
- * [103.04,7],[103.24,4],[103.34,7],[103.44,6]
- * ]
- * }
- * ```
- *
- * @param {object} data Data to load as per required format.
- * @param {CIQ.ChartEngine.Chart} chart The chart whose market data to update. Defaults to the instance chart.
- * @param {string} symbol Symbol if passing secondary series information
- * @param {object} params Additional parameters
- * @param {boolean} [params.fromTrade] This function can be called directly or as a result of a trade update, such as from {@link CIQ.ChartEngine.Chart#updateChartData}.
- * Set this param to `true` to indicate the incoming data is a master data record.
- * Otherwise the function will attempt to adjust the record date to align with the last bar.
- * @param {boolean} [params.finalClose] If the data.Close is being manipulated (such as with animation), this param should contain the real, final Close value
- * @memberof CIQ.ChartEngine
- * @since
- * - 6.1.0
- * - 6.1.1 Added `params.fromTrade`.
- * - 6.2.3 Added `params.finalClose`.
- */
- CIQ.ChartEngine.prototype.updateCurrentMarketData = function (
- data,
- chart,
- symbol,
- params
- ) {
- if (!data || !data.DT) return;
- if (!chart) chart = this.chart;
- var calledFromTrade = params && params.fromTrade;
- // find the right bar for the data, if not found already
- var timestamp = data.DT;
- if (!calledFromTrade && this.layout.interval != "tick" && chart.market) {
- if (chart.market.market_def) {
- if (!chart.market.isMarketDate(data.DT)) return; // non-market date, disregard
- if (
- !CIQ.ChartEngine.isDailyInterval(this.layout.interval) &&
- chart.market.getSession(data.DT) === null
- )
- return; // outside of market hours, disregard
- }
- // Find the latest possible bar for this data, including after hours
- // Iterate off a copy so we don't mess with the chart's market's settings!
- var iter_parms = {
- begin: data.DT,
- interval: this.layout.interval,
- periodicity: this.layout.periodicity,
- timeUnit: this.layout.timeUnit
- };
- var market = new CIQ.Market(chart.market.market_def);
- if (this.extendedHours && this.extendedHours.filter)
- market.enableAllAvailableSessions();
- var iter = market.newIterator(iter_parms);
- iter.next();
- data.DT = iter.previous();
- }
-
- if (this.runPrepend("updateCurrentMarketData", arguments)) return;
- var currentMarketData = chart.currentMarketData;
- if (symbol) {
- if (!currentMarketData[symbol]) currentMarketData[symbol] = {};
- currentMarketData = currentMarketData[symbol];
- }
- ["Last", "Bid", "Ask"].forEach(function (i) {
- if (data[i] && typeof data[i] == "number") {
- if (
- !currentMarketData[i] ||
- !currentMarketData[i].DT ||
- currentMarketData[i].DT <= data.DT
- ) {
- currentMarketData[i] = {
- DT: data.DT,
- Price: data[i],
- Size: data[i + "Size"],
- Timestamp: timestamp
- };
- }
- }
- });
- ["BidL2", "AskL2"].forEach(function (i) {
- if (data[i] && data[i] instanceof Array) {
- if (
- !currentMarketData[i] ||
- !currentMarketData[i].DT ||
- currentMarketData[i].DT <= data.DT
- ) {
- currentMarketData[i] = {
- DT: data.DT,
- Price_Size: data[i],
- Timestamp: timestamp
- };
- }
- }
- });
- if (
- data.Close &&
- (!currentMarketData.Last || currentMarketData.Last.DT <= data.DT)
- ) {
- var close = data.Close,
- finalClose = params && params.finalClose;
- if (finalClose || finalClose === 0) close = finalClose;
- currentMarketData.Last = {
- DT: data.DT,
- Price: close,
- Size:
- data.LastSize === undefined && this.layout.interval == "tick"
- ? data.Volume
- : data.LastSize,
- Timestamp: data.LastTime || timestamp
- };
- }
- currentMarketData.touched = new Date(); // so we can observe it
-
- if (!calledFromTrade) delete data.Last; // can cause problems in injections if left
-
- this.runAppend("updateCurrentMarketData", arguments);
- };
-
- /**
- * INJECTABLE
- *
- * Clears the [chart.currentMarketData]{@link CIQ.ChartEngine.Chart#currentMarketData} object or the one linked to a secondary series, if one provided.
- * @param {CIQ.ChartEngine.Chart} chart The chart to clear. If omitted, will clear all charts.
- * @param {string} symbol Symbol to clear this symbol's secondary series information
- * @memberof CIQ.ChartEngine
- * @since 6.1.0
- */
- CIQ.ChartEngine.prototype.clearCurrentMarketData = function (chart, symbol) {
- if (this.runPrepend("clearCurrentMarketData", arguments)) return;
- var ch,
- charts = [];
- if (!chart) {
- for (ch in this.charts) {
- charts.push(this.charts[ch]);
- }
- } else {
- charts.push(chart);
- }
- for (ch = 0; ch < charts.length; ch++) {
- var md = charts[ch].currentMarketData;
- if (symbol) {
- delete md[symbol];
- } else {
- // preserve original object as it's being observed
- for (var d in md) {
- md[d] = undefined;
- }
- }
- }
- this.runAppend("clearCurrentMarketData", arguments);
- };
-
- };
-
-
- let __js_core_engine_event_ = (_exports) => {
-
-
- var CIQ = _exports.CIQ;
-
- /**
- * Legacy method used to internally dispatch a registered event whenever a change to layout, drawings or theme occurs.
- * Events must be registered using {@link CIQ.ChartEngine#addDomEventListener} for "layout", "drawing", "theme" and "preferences".
- *
- * This is simply a proxy method that calls the corresponding {@link CIQ.ChartEngine#dispatch} method.
- *
- * Developers creating their own custom functionality should call {@link CIQ.ChartEngine#dispatch} instead.
- *
- * @param {string} change Type of change that occurred.
- * @memberof CIQ.ChartEngine
- */
- CIQ.ChartEngine.prototype.changeOccurred = function (change) {
- var obj = {
- stx: this,
- symbol: this.chart.symbol,
- symbolObject: this.chart.symbolObject,
- layout: this.layout,
- drawings: this.drawingObjects
- };
- if (change == "theme") this.dispatch("theme", obj);
- if (this.currentlyImporting) return; // changes actually occurring because of an import, not user activity
- if (change == "layout") {
- this.dispatch("layout", obj);
- } else if (change == "vector") {
- this.dispatch("drawing", obj);
- } else if (change == "preferences") {
- this.dispatch("preferences", obj);
- }
- };
-
- /**
- * Charts may require asynchronous data to render. This creates a dilemma for any external
- * process that depends on a fully rendered chart (for instance a process to turn a chart into an image).
- * To solve this problem, external processes can register for a callback which will tell them when the chart
- * has been drawn. See {@link CIQ.ChartEngine.registerChartDrawnCallback}.
- *
- * To accommodate this requirement, studies, plugins or injections that render asynchronously should use startAsyncAction
- * and {@link CIQ.ChartEngine#completeAsyncAction} to inform the chart of their asynchronous activity.
- * @memberof CIQ.ChartEngine
- */
- CIQ.ChartEngine.prototype.startAsyncAction = function () {
- if (!this.pendingAsyncs) this.pendingAsyncs = [];
- this.pendingAsyncs.push(true);
- };
-
- /**
- * Registers a callback for when the chart has been drawn
- * @param {function} fc The function to call
- * @return {object} An object that can be passed in to {@link CIQ.ChartEngine#unregisterChartDrawnCallback}
- * @memberof CIQ.ChartEngine
- */
- CIQ.ChartEngine.prototype.registerChartDrawnCallback = function (fc) {
- if (!this.asyncCallbacks) this.asyncCallbacks = [];
- this.asyncCallbacks.push(fc);
- return {
- fc: fc
- };
- };
-
- /**
- * Removes a callback registration for when the chart has been drawn
- * @param {object} obj An object from {@link CIQ.ChartEngine#registerDrawnCallback}
- * @memberof CIQ.ChartEngine
- */
- CIQ.ChartEngine.prototype.unregisterChartDrawnCallback = function (obj) {
- for (var i = 0; i < this.asyncCallbacks.length; i++) {
- if (this.asyncCallbacks[i] == obj.fc) {
- this.asyncCallbacks.splice(i, 1);
- return;
- }
- }
- };
-
- /**
- * Makes the async callbacks only if no pending async activity
- * @memberof CIQ.ChartEngine
- */
- CIQ.ChartEngine.prototype.makeAsyncCallbacks = function () {
- if (!this.asyncCallbacks) return; // no callbacks to make
- if (!this.pendingAsyncs || !this.pendingAsyncs.length) {
- // If no pending asyncs, or the array is empty (all have been fulfilled)
- for (var i = 0; i < this.asyncCallbacks.length; i++) {
- this.asyncCallbacks[i]();
- }
- }
- };
- /**
- * Studies or plugins that use asynchronous data should call this when their async activities are complete.
- * See {@link CIQ.ChartEngine#startAsyncAction}
- * @memberof CIQ.ChartEngine
- */
- CIQ.ChartEngine.prototype.completeAsyncAction = function () {
- this.pendingAsyncs.pop();
- this.makeAsyncCallbacks();
- };
-
- /**
- * Add a DOM element's event listener and index it so that it will be removed when invoking CIQ.ChartEngine.destroy().
- * https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener
- * @param {element} element DOM element to listen for changes on
- * @param {string} event The event type to listen for. Possible values: https://developer.mozilla.org/en-US/docs/Web/Events
- * @param {function} listener The callback to invoke when the event happens.
- * @param {*} Either a boolean or object. See addEventListener options.
- * @see {@link CIQ.ChartEngine#destroy}
- * @private
- * @since 3.0.0
- */
- CIQ.ChartEngine.prototype.addDomEventListener = function (
- element,
- event,
- listener,
- options
- ) {
- element.addEventListener(event, listener, options);
- this.eventListeners.push({
- element: element,
- event: event,
- function: listener,
- options: options
- });
- };
-
- /**
- * Registers a listener for a chart event in the chart engine instance.
- *
- * Events are tracked in the `CIQ.ChartEngine.callbackListeners` object, which is READ ONLY and
- * should never be manually altered.
- *
- * Valid event types and listeners:
- * - `*`: Passing in this value registers the listener to every event type below.
- * - `doubleTap`: [doubleTapEventListener]{@link CIQ.ChartEngine~doubleTapEventListener}
- * - `doubleClick`: [doubleClickEventListener]{@link CIQ.ChartEngine~doubleClickEventListener}
- * - `drawing`: [drawingEventListener]{@link CIQ.ChartEngine~drawingEventListener}
- * - `drawingEdit`: [drawingEditEventListener]{@link CIQ.ChartEngine~drawingEditEventListener}
- * - `floatingWindow`: [floatingWindowEventListener]{@link CIQ.ChartEngine~floatingWindowEventListener}
- * - `layout`: [layoutEventListener]{@link CIQ.ChartEngine~layoutEventListener}
- * - `longhold`: [longholdEventListener]{@link CIQ.ChartEngine~longholdEventListener}
- * - `move`: [moveEventListener]{@link CIQ.ChartEngine~moveEventListener}
- * - `newChart`: [newChartEventListener]{@link CIQ.ChartEngine~newChartEventListener}
- * - `notification`: [notificationEventListener]{@link CIQ.ChartEngine~notificationEventListener}
- * - `periodicity`: [periodicityEventListener]{@link CIQ.ChartEngine~periodicityEventListener}
- * - `preferences`: [preferencesEventListener]{@link CIQ.ChartEngine~preferencesEventListener}
- * - `rightClick`: [rightClickEventListener]{@link CIQ.ChartEngine~rightClickEventListener}
- * - `scroll`: [scrollEventListener]{@link CIQ.ChartEngine~scrollEventListener}
- * - `studyOverlayEdit`: [studyOverlayEditEventListener]{@link CIQ.ChartEngine~studyOverlayEditEventListener}
- * - `studyPanelEdit`: [studyPanelEditEventListener]{@link CIQ.ChartEngine~studyPanelEditEventListener}
- * - `symbolChange`: [symbolChangeEventListener]{@link CIQ.ChartEngine~symbolChangeEventListener}
- * - `symbolImport`: [symbolImportEventListener]{@link CIQ.ChartEngine~symbolImportEventListener}
- * - `tap`: [tapEventListener]{@link CIQ.ChartEngine~tapEventListener}
- * - `theme`: [themeEventListener]{@link CIQ.ChartEngine~themeEventListener}
- * - `undoStamp`: [undoStampEventListener]{@link CIQ.ChartEngine~undoStampEventListener}
- *
- * @param {string|string[]} type One or more event types to listen for. See the description above
- * for valid types.
- * @param {function} callback The listener to call when the event or events specified by `type`
- * are triggered. Accepts an object argument containing properties specified in the event
- * listener definition.
- * @return {object} An object containing `type` and `callback`. The object can be passed to
- * {@link CIQ.ChartEngine#removeEventListener} to remove the listener.
- *
- * @memberof CIQ.ChartEngine
- * @since
- * - 04-2016-08
- * - 4.0.0 Added "doubleTap".
- * - 4.0.0 Type can be an array of event options.
- * - 6.3.0 Added "scroll".
- * - 7.0.0 Added "preferences" and "drawingEdit".
- * - 8.1.0 Added "periodicity".
- * - 8.2.0 Added "notification" and "floatingWindow".
- *
- * @example
- * The high and low values for the visible section of the primary chart are calculated and corresponding values stored as follows:
- * - `chart.highValue` - The highest value on the chart
- * - `chart.lowValue` - The lowest value on the chart
- *
- * See {@link CIQ.ChartEngine.Chart#includeOverlaysInMinMax} and {@link CIQ.ChartEngine#determineMinMax}
- *
- * Those values are subsequently used by {@link CIQ.ChartEngine.AdvancedInjectable#createYAxis} which is called from within this method.
- * This method also calls {@link CIQ.ChartEngine.AdvancedInjectable#createCrosshairs}.
- *
- * @param {CIQ.ChartEngine.Chart} chart The chart to initialize
- * @memberof CIQ.ChartEngine.AdvancedInjectable#
- * @alias initializeDisplay
- * @since 5.2.0. It now also calculates the minimum and maximum points in all study panels. This calculation was previously done using {@link CIQ.Studies.determineMinMax}, now deprecated.
- */
- CIQ.ChartEngine.prototype.initializeDisplay = function (chart) {
- if (this.runPrepend("initializeDisplay", arguments)) return;
- let fields = [],
- useSum = [],
- checkArray = false;
- const self = this;
- const baseOHLCFields = ["Close", "Open", "High", "Low"];
- const baseLineFields = [chart.defaultPlotField || "Close"];
- const { mainSeriesRenderer } = this;
- const { dataSegment, seriesRenderers } = chart;
- function setYAxisFields(yAxis, panel) {
- // first see if this is an axis for a study; if so, get the fields
- let isStudyAxis = false;
- const sd =
- self.layout && self.layout.studies && self.layout.studies[yAxis.name];
- if (sd && (!panel || panel.name == sd.panel)) {
- for (const j in sd.outputMap) {
- fields.push(j);
- if (sd.study) {
- if (sd.study.renderer) {
- // if there is a study renderer, just assume it requires OHLC regardless of the renderer type
- fields = fields.concat(
- CIQ.createObjectChainNames(j, baseOHLCFields)
- );
- } else if (!sd.study.seriesFN) {
- // no seriesFN, assume it's a line and needs only Close
- fields = fields.concat(
- CIQ.createObjectChainNames(j, baseLineFields)
- );
- }
- }
- }
- for (let h = 0; h <= 2; h++)
- fields.push(sd.name + "_hist" + (h ? h : ""));
- isStudyAxis = true;
- }
- if (!panel) return; //to end recursion from includeOverlaysInMinMax below
-
- yAxis.studies = [];
- yAxis.renderers = [];
- if (isStudyAxis) {
- yAxis.studies.push(yAxis.name);
- }
- // then check renderers and add fields for each series in the renderer using this yaxis
- for (const id in seriesRenderers) {
- const renderer = seriesRenderers[id],
- params = renderer.params,
- panelName = params.panel;
- if (
- (params.yAxis ||
- !self.panels[panelName] ||
- self.panels[panelName].yAxis) != yAxis
- )
- continue;
- if (panelName != panel.name) continue;
- const baseFields = renderer.highLowBars ? baseOHLCFields : baseLineFields;
- checkArray = renderer.bounded;
- for (let id2 = 0; id2 < renderer.seriesParams.length; id2++) {
- // Find any series that share the Y axis
- const seriesParams = renderer.seriesParams[id2];
- if (seriesParams.hidden) continue;
- let fieldNamesToConcat;
- if (seriesParams.subField) {
- fieldNamesToConcat = CIQ.createObjectChainNames(seriesParams.symbol, [
- seriesParams.subField
- ]).concat(seriesParams.symbol);
- } else if (seriesParams.symbol) {
- fieldNamesToConcat = CIQ.createObjectChainNames(
- seriesParams.symbol,
- baseFields
- ).concat(seriesParams.symbol);
- } else if (seriesParams.field) {
- fieldNamesToConcat = seriesParams.field;
- } else if (yAxis == chart.panel.yAxis) {
- // only if the main chart panel's yAxis include baseFields
- fieldNamesToConcat = baseFields;
- }
- if (fieldNamesToConcat) fields = fields.concat(fieldNamesToConcat);
- if (renderer.useSum) useSum = useSum.concat(fieldNamesToConcat);
- }
- yAxis.renderers.push(id);
- }
- // Finally add any fields used by overlay studies
- for (const overlay in self.overlays) {
- const o = self.overlays[overlay];
- if (o.panel != panel.name) continue;
- if (o.name == yAxis.name) continue; // don't loop thru the same axis twice and create duplicates
- const oAxis = o.getYAxis(self);
- if (oAxis != yAxis) continue;
- yAxis.studies.push(o.name);
- if (chart.includeOverlaysInMinMax) {
- setYAxisFields({ name: o.name });
- }
- }
- }
- let minMax;
- let length = null;
-
- // We often have an extra tick hanging off the edge of the screen. We don't want this
- // tick to affect the high and low calculation though. That causes jumpiness when
- // zooming because the chart is alternately including and excluding that tick
- let ticksOnScreen = Math.floor(
- (chart.width - this.micropixels) / this.layout.candleWidth
- );
- if (chart.scroll > chart.maxTicks && chart.maxTicks > ticksOnScreen + 1)
- length = dataSegment.length - 1;
-
- let arr = [];
- for (const p in this.panels) {
- const myPanel = this.panels[p];
- arr = myPanel.yaxisLHS.concat(myPanel.yaxisRHS);
- for (let y = 0; y < arr.length; y++) {
- const yAxis = arr[y];
- fields = [];
- useSum = [];
- const doTransform = chart.transformFunc && yAxis == chart.panel.yAxis;
- setYAxisFields(yAxis, myPanel);
- // maintenance of axes here
- if (
- !this.currentlyImporting &&
- !yAxis.renderers.length &&
- !yAxis.studies.length
- ) {
- this.deleteYAxisIfUnused(myPanel, yAxis);
- continue;
- }
- if (mainSeriesRenderer && mainSeriesRenderer.determineMax) {
- minMax = mainSeriesRenderer.determineMax(
- dataSegment,
- fields,
- useSum,
- !doTransform,
- length,
- checkArray,
- myPanel,
- yAxis
- );
- } else {
- minMax = this.determineMinMax(
- dataSegment,
- fields,
- useSum,
- !doTransform,
- length,
- checkArray,
- myPanel,
- yAxis
- );
- }
-
- if (this.baselineHelper) minMax = this.setBaselineMinMax(minMax, yAxis);
-
- yAxis.lowValue = minMax[0];
- yAxis.highValue = minMax[1];
- if (yAxis == chart.panel.yAxis) {
- chart.lowValue = yAxis.lowValue;
- chart.highValue = yAxis.highValue;
- }
- }
- }
- const aggregation = chart.state.aggregation;
- if (aggregation && aggregation.box) {
- // Make room for X and O rendering since half of it lies beyond the high/low
- chart.lowValue -= aggregation.box / 2;
- chart.highValue += aggregation.box / 2;
- }
-
- this.runAppend("initializeDisplay", arguments);
- };
-
- /**
- * Sets the market definition on the chart.
- *
- * Once set, the definition will not change until it is explicitly set to something else by calling this method again.
- *
- * A new definition for a chart should only be set once, right before a new instrument is loaded with the {@link CIQ.ChartEngine#loadChart} call.
- * Loading or modifying a market definition after a chart has loaded its data will result in unpredictable results.
- *
- * If a dynamic model is desired, where a new definition is loaded as different instruments are activated, see {@link CIQ.ChartEngine#setMarketFactory}.
- *
- * See {@link CIQ.Market} for market definition rules and examples.
- *
- * This is only required if your chart will need to know the operating hours for the different exchanges.
- *
- * If using a 24x7 chart, a market does not need to be set.
- * @param {object} marketDefinition A market definition as required by {@link CIQ.Market}
- * @param {CIQ.ChartEngine.Chart} chart An instance of {@link CIQ.ChartEngine.Chart}
- * @memberof CIQ.ChartEngine
- * @since 04-2016-08
- * @example
- * stxx.setMarket({
- * name: 'My_Market',
- * market_tz: 'My_Timezone', // Note you must specify the time zone for the market!
- * rules: [
- * { 'dayofweek': 1, 'open': '08:00', 'close': '14:30' },
- * { 'dayofweek': 2, 'open': '08:00', 'close': '14:30' },
- * { 'dayofweek': 3, 'open': '08:00', 'close': '14:30' },
- * { 'dayofweek': 4, 'open': '08:00', 'close': '14:30' },
- * { 'dayofweek': 5, 'open': '08:00', 'close': '14:30' },
- * ],
- * });
- */
- CIQ.ChartEngine.prototype.setMarket = function (marketDefinition, chart) {
- if (!CIQ.Market) return;
- if (!chart) chart = this.chart;
- chart.market = new CIQ.Market(marketDefinition);
- for (var session in this.layout.marketSessions) {
- chart.market.disableSession(session, this.layout.marketSessions[session]);
- }
- };
-
- /**
- * Links the chart to a method that given a symbol object of form accepted by {@link CIQ.ChartEngine#loadChart}, can return a complete market definition object.
- * Once linked, the market factory it will be used by the chart to ensure the market always matches the active instrument.
- * This is only required if your chart will need to know the operating hours for the different exchanges.
- * If using a 24x7 chart, a market factory does not need to be set.
- *
- * Please note that if using the default sample templates, this method is set to use the {@link CIQ.Market.Symbology} functions, which must be reviewed and adjust to comply with your quote feed and symbology format before they can be used.
- * @param {function} factory A function that takes a symbolObject and returns a market definition. See {@link CIQ.Market} for instruction on how to create a market definition. See {@link CIQ.Market.Symbology.factory} for working example of a factory function.
- * @memberof CIQ.ChartEngine
- * @since 04-2016-08
- * @example
- * // example of a market factory that returns a different market definition based on the symbol passed in
- * sampleFactory=function(symbolObject){
- * var symbol=symbolObject.symbol;
- * // isTypeX(symbol) is a function you would create to identify the market definition object that should be used.
- * if( isType1(symbol) ) return type1DefinitionObject;
- * if( isType2(symbol) ) return type2DefinitionObject;
- * if( isType3(symbol) ) return type3DefinitionObject;
- * return defaultDefinitionObject;
- * };
- *
- * var stxx=new CIQ.ChartEngine({container:document.querySelector(".chartContainer"), preferences:{labels:false, currentPriceLine:true, whitespace:0}});
- * stxx.setMarketFactory(sampleFactory);
- */
- CIQ.ChartEngine.prototype.setMarketFactory = function (factory) {
- this.marketFactory = factory;
- };
-
- /**
- * Sets a timer to check for chart resizing.
- *
- * Normally, the chart is resized whenever the screen is resized by capturing a screen resize event.
- * However, if charts are embedded in a windowing GUI, they may not receive window resize events.
- * Ideally, `stxx.resizeChart()` should be called whenever a window is resized; however, if this is inconvenient,
- * then the resize timer can be enabled to cover all bases.
- *
- * On initialization, CIQ.ChartEngine.resizeDetectMS is checked for the default resize checking interval. The default is 1,000 milliseconds.
- * To turn off resize checking simply set CIQ.ChartEngine.resizeDetectMS=0; when you declare your CIQ.ChartEngine object.
- *
- * @param {number} ms Number of milliseconds to poll. Zero to stop checking.
- * @memberof CIQ.ChartEngine
- * @since 7.2.0 For browsers that support it, a [ResizeObserver](https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver) is used instead of a timeout.
- */
-
- CIQ.ChartEngine.prototype.setResizeTimer = function (ms) {
- this.resizeDetectMS = ms;
- function closure(self, useObserver) {
- var f = function () {
- if (!self.chart.canvas) return;
- if (!CIQ.isAndroid) {
- if (
- self.chart.canvas.height !=
- Math.floor(
- self.devicePixelRatio * self.chart.container.clientHeight
- ) ||
- self.chart.canvas.width !=
- Math.floor(self.devicePixelRatio * self.chart.container.clientWidth)
- ) {
- self.resizeChart();
- }
- }
- };
- return function () {
- // Adding throttling here to fix Chrome issue where benign error is sometimes thrown "ResizeObserver loop limit exceeded"
- // Nevertheless, error seems to be caught by Karma and unit tests fail
- // https://github.com/KingSora/OverlayScrollbars/issues/90
- if (typeof ResizeObserver !== "undefined") {
- if (CIQ.ChartEngine.useAnimation) {
- requestAnimationFrame(f);
- } else {
- setTimeout(f, 0);
- }
- } else f();
- };
- }
- this.resizeHandle = CIQ.resizeObserver(
- this.chart.container,
- closure(this),
- this.resizeHandle,
- ms
- );
- };
-
- /**
- * Returns an array of all the securities, series, and overlays that are drawn on the current panel.
- *
- * @memberof CIQ.ChartEngine
- * @returns {object[]} The fields — in object-chain form — of the currently rendered objects.
- * @since 7.2.0
- */
- CIQ.ChartEngine.prototype.getRenderedItems = function () {
- var chart = this.chart,
- currentPanel = this.currentPanel;
- if (!currentPanel) return;
-
- var ohlc = ["Open", "High", "Low", "Close"];
- var close = ["Close"];
- var rendered = [];
- for (var o in this.overlays) {
- if (this.overlays[o].panel !== currentPanel.name) continue;
- // use the keys so if we ever change how the output map is constructed we don't need to change it twice
- rendered = rendered.concat(Object.keys(this.overlays[o].outputMap));
- }
- for (var r in chart.seriesRenderers) {
- var renderer = chart.seriesRenderers[r];
- if (renderer.params.panel != currentPanel.name) continue;
- for (var rs in renderer.seriesParams) {
- var sp = renderer.seriesParams[rs];
- var baseFields = renderer.highLowBars ? ohlc : close;
- if (sp.subField) {
- rendered = rendered
- .concat(CIQ.createObjectChainNames(sp.symbol, [sp.subField]))
- .concat(sp.symbol);
- } else if (sp.symbol) {
- rendered = rendered
- .concat(CIQ.createObjectChainNames(sp.symbol, baseFields))
- .concat(sp.symbol);
- } else if (sp.field) {
- rendered.push(sp.field);
- } else if (currentPanel == chart.panel) {
- // only if on main chart panel include baseFields
- rendered = rendered.concat(baseFields);
- }
- }
- }
- return rendered;
- };
-
- /**
- * Sets a transformation and untransformation function. Transforms can be used to transform the Y-Axis from absolute
- * to relative values. For instance, comparison charts use a transform that adjusts from price to percentage.
- * After this is called, chart.transformFunc and chart.untransformFunc will be set to those functions.
- * @param {CIQ.ChartEngine.Chart} chart The chart to transform
- * @param {function} transformFunction A transformation callback function which takes a number and returns the transformation of that number
- * @param {function} untransformFunction An untransformation callback function
- * @memberof CIQ.ChartEngine
- */
- CIQ.ChartEngine.prototype.setTransform = function (
- chart,
- transformFunction,
- untransformFunction
- ) {
- chart.transformFunc = transformFunction;
- chart.untransformFunc = untransformFunction;
- };
-
- /**
- * Removes a transformation/untransformation pair
- * @param {CIQ.ChartEngine.Chart} chart The chart to remove transformations from
- * @memberof CIQ.ChartEngine
- */
- CIQ.ChartEngine.prototype.unsetTransform = function (chart) {
- delete chart.transformFunc;
- delete chart.untransformFunc;
- for (var i = 0; chart.dataSet && i < chart.dataSet.length; i++) {
- chart.dataSet[i].transform = null;
- }
- };
-
- CIQ.ChartEngine.prototype.isEquationChart = function (symbol) {
- if (symbol && symbol[0] == "=") {
- if (!this.allowEquations || !CIQ.fetchEquationChart) {
- console.warn(
- "Error, equation chart option requires equationsAdvanced.js"
- );
- return false;
- }
- return true;
- }
- return false;
- };
-
- /**
- * INJECTABLE
- * Animation Loop
- *
- * This method ensures that the chart is not scrolled off of either of the vertical edges.
- * See {@link CIQ.ChartEngine#minimumLeftBars}, {@link CIQ.ChartEngine.Chart#allowScrollPast}, and {@link CIQ.ChartEngine.Chart#allowScrollFuture} for adjustments to defaults.
- * @param {CIQ.ChartEngine.Chart} theChart The chart to check
- * @memberof CIQ.ChartEngine.AdvancedInjectable#
- * @alias correctIfOffEdge
- */
- CIQ.ChartEngine.prototype.correctIfOffEdge = function (theChart) {
- if (this.runPrepend("correctIfOffEdge", arguments)) return;
- for (var chartName in this.charts) {
- var chart = this.charts[chartName],
- dataSet = chart.dataSet,
- maxTicks = chart.maxTicks,
- layout = this.layout;
-
- var minimumLeftBars = this.minimumLeftBars;
-
- var leftPad = Math.min(minimumLeftBars, maxTicks); // in case the minimumLeftBars is larger than what we can display
- if (chart.allowScrollPast) {
- // allow scrolling from left to right, creating white space on either side
- var rightPad = maxTicks - leftPad;
- if (leftPad > dataSet.length) {
- rightPad = maxTicks - dataSet.length;
- }
- if (chart.scroll - rightPad >= dataSet.length) {
- chart.scroll = dataSet.length + rightPad - 1;
- this.micropixels = 0;
- }
- if (chart.scroll <= leftPad) {
- chart.scroll = leftPad;
- this.micropixels = 0;
- }
- } else {
- // earliest point in time is always anchored on left side of chart
- if (chart.scroll < leftPad) {
- chart.scroll = leftPad;
- }
- if (chart.scroll > dataSet.length) {
- chart.scroll = dataSet.length;
- }
- }
- if (chart.allowScrollFuture === false) {
- var whitespace =
- this.getLabelOffsetInPixels(chart, layout.chartType) +
- layout.candleWidth * chart.whiteSpaceFutureTicks;
- var barsOnScreen =
- maxTicks - Math.round(whitespace / layout.candleWidth) - 1;
- var scroll = this.micropixels < 0 ? chart.scroll - 1 : chart.scroll;
- if (scroll < barsOnScreen) {
- chart.scroll = barsOnScreen;
- this.micropixels = 0;
- }
- }
- }
- this.runAppend("correctIfOffEdge", arguments);
- };
-
- /**
- * Returns the offset from the left side of the screen for the first element
- * on the chart screen. Most times this will be zero except when a user has scrolled
- * past the end of the chart in which case it will be a positive number. This can be used
- * to recreate a saved chart.
- * @return {number} The offset from the left of the chart.
- * @memberof CIQ.ChartEngine
- */
- CIQ.ChartEngine.prototype.getStartDateOffset = function () {
- for (var ds = 0; ds < this.chart.dataSegment.length; ds++) {
- if (this.chart.dataSegment[ds]) {
- return ds;
- }
- }
- return 0;
- };
-
- /**
- * Scrolls the chart so that the leftmost tick is the requested date.
- * The date must be an exact match and data for that bar must already be loaded in the chart.
- * There is no effect if the date is not found an the engine will not attempt to fetch more data.
- * @param {date} dt The requested date
- * @memberof CIQ.ChartEngine
- */
- CIQ.ChartEngine.prototype.setStartDate = function (dt) {
- for (var i = 0; i < this.chart.dataSet.length; i++) {
- var bar = this.chart.dataSet[i];
- if (bar.DT.getTime() == dt.getTime()) {
- this.chart.scroll = this.chart.dataSet.length - i;
- this.draw();
- return;
- }
- }
- };
-
- //@private
- CIQ.ChartEngine.prototype.clearPixelCache = function () {
- for (var x in this.panels) {
- var panel = this.panels[x];
- panel.cacheHigh = null;
- panel.cacheLow = null;
- panel.cacheLeft = 1000000;
- panel.cacheRight = -1;
- }
- for (var chartName in this.charts) {
- var chart = this.charts[chartName];
- if (!chart.dataSet) continue;
- for (var i = 0; i < chart.dataSet.length; i++) {
- chart.dataSet[i].cache = {};
- }
- }
- };
-
- /**
- * This method adjusts the canvas for the current backing store. The backing store is used on "retina" style devices
- * to indicate the ratio of actual screen pixels to web pixels. The canvas is adjusted according to this ratio so that
- * pixels appear at the expected size and aren't fuzzy. Note that backing store is sometimes also employed by browsers
- * to change the size of the view.
- * @private
- * @param {HTMLCanvasElement} canvas An HTML5 canvas
- * @param {external:CanvasRenderingContext2D} context An HTML5 canvas context
- * @memberof CIQ.ChartEngine
- */
- CIQ.ChartEngine.prototype.adjustBackingStore = function (canvas, context) {
- this.devicePixelRatio = window.devicePixelRatio || 1;
- //note, let's ignore DPR<1, it is not consistently implemented on all browsers between retina and nonretina displays
- if (this.devicePixelRatio < 1.0) this.devicePixelRatio = 1.0;
- var backingStoreRatio =
- context.webkitBackingStorePixelRatio ||
- context.mozBackingStorePixelRatio ||
- context.msBackingStorePixelRatio ||
- context.oBackingStorePixelRatio ||
- context.backingStorePixelRatio ||
- 1;
-
- var ratio = this.devicePixelRatio / backingStoreRatio;
-
- if (!this.useBackingStore) {
- this.devicePixelRatio = this.adjustedDisplayPixelRatio = 1;
- return;
- }
- if (!CIQ.isAndroid || CIQ.is_chrome || CIQ.isFF) {
- var oldWidth = canvas.width;
- var oldHeight = canvas.height;
-
- canvas.width = oldWidth * ratio;
- canvas.height = oldHeight * ratio;
-
- canvas.style.width = oldWidth + "px";
- canvas.style.height = oldHeight + "px";
-
- context.scale(ratio, ratio);
- this.adjustedDisplayPixelRatio = ratio;
- this.backing = {
- ratio: ratio,
- width: canvas.width,
- height: canvas.height,
- styleWidth: oldWidth,
- styleHeight: oldHeight
- };
- }
- };
-
- CIQ.ChartEngine.prototype.reconstituteBackingStore = function () {
- if (!this.useBackingStore || !this.backing) return;
- var canvases = [this.chart.canvas];
- if (this.useBackgroundCanvas) canvases.push(this.chart.backgroundCanvas);
- var backing = this.backing;
- canvases.forEach(function (canvas) {
- if (canvas.width == backing.width) return;
-
- canvas.width = backing.width;
- canvas.height = backing.height;
-
- canvas.context.scale(backing.ratio, backing.ratio);
- });
- this.adjustedDisplayPixelRatio = backing.ratio;
- this.draw();
- };
-
- CIQ.ChartEngine.prototype.disableBackingStore = function () {
- if (!this.useBackingStore || !this.backing) return;
- var canvases = [this.chart.canvas];
- if (this.useBackgroundCanvas) canvases.push(this.chart.backgroundCanvas);
- var backing = this.backing;
- canvases.forEach(function (canvas) {
- if (canvas.width == backing.styleWidth) return;
-
- canvas.width = backing.styleWidth;
- canvas.height = backing.styleHeight;
-
- canvas.context.scale(1, 1);
- });
- this.adjustedDisplayPixelRatio = 1;
- this.draw();
- };
-
- /**
- * Determines the appropriate canvas on which to draw background plots (gridlines and axes). If
- * {@link CIQ.ChartEngine#useBackgroundCanvas} is true, background plots are drawn on the chart
- * background canvas; if false, on the chart main canvas.
- *
- * @param {CIQ.ChartEngine.Chart} chart The chart from which the canvas is obtained.
- * @return {HTMLElement} Either the chart's main canvas or background canvas, depending
- * on the value of {@link CIQ.ChartEngine#useBackgroundCanvas}.
- * @memberof CIQ.ChartEngine
- * @since 7.4.0
- */
- CIQ.ChartEngine.prototype.getBackgroundCanvas = function (chart) {
- if (!chart) chart = this.chart;
- return this.useBackgroundCanvas ? chart.backgroundCanvas : chart.canvas;
- };
-
- /**
- * This method resizes the canvas to the dimensions of the containing div. This is called primarily
- * by {@link CIQ.ChartEngine#resizeChart} and also when the chart is initialized (via loadChart).
- * @memberof CIQ.ChartEngine
- */
- CIQ.ChartEngine.prototype.resizeCanvas = function () {
- var canvas = this.chart.canvas;
- var context = this.chart.context;
- if (canvas && context) {
- this.floatCanvas.height = this.chart.tempCanvas.height = this.chart.backgroundCanvas.height = canvas.height = this.chart.container.clientHeight;
- this.floatCanvas.width = this.chart.tempCanvas.width = this.chart.backgroundCanvas.width = canvas.width = this.chart.container.clientWidth;
- this.adjustBackingStore(canvas, context);
- this.adjustBackingStore(
- this.chart.tempCanvas,
- this.chart.tempCanvas.context
- );
- this.adjustBackingStore(this.floatCanvas, this.floatCanvas.context);
- this.adjustBackingStore(
- this.chart.backgroundCanvas,
- this.chart.backgroundCanvas.context
- );
- }
- var rect = this.container.getBoundingClientRect();
- this.top = rect.top;
- this.left = rect.left;
- this.canvasWidth = this.chart.canvasWidth = this.chart.container.clientWidth;
- this.right = this.left + this.canvasWidth;
- this.height = this.chart.container.clientHeight;
- this.width = this.right - this.left;
- if (
- this.width === 0 &&
- !this.container.dimensionlessCanvas &&
- this.container.closest("html")
- ) {
- console.log("warning: zero width chart. Check CSS for chart container.");
- }
- this.bottom = this.top + this.height;
- this.calculateYAxisPositions();
- this.chart.canvasRight = this.right;
- this.chart.canvasHeight = this.height;
- var candleWidth = this.layout.candleWidth;
- if (typeof candleWidth == "undefined") candleWidth = 8;
- for (var chartName in this.charts) {
- var chart = this.charts[chartName];
-
- this.setCandleWidth(candleWidth, chart);
- if (chart.scroll < chart.width / candleWidth) {
- chart.scroll = Math.floor(chart.width / candleWidth);
- var wsInTicks = Math.round(
- this.preferences.whitespace / this.layout.candleWidth
- );
- chart.scroll -= wsInTicks;
- }
-
- var idealNumberOfTicks = 10;
- var appxLabelWidth;
- try {
- appxLabelWidth = context.measureText("10:00").width * 2;
- } catch (e) {
- appxLabelWidth = 100;
- }
- while (idealNumberOfTicks > 1) {
- if (this.chart.width / appxLabelWidth > idealNumberOfTicks) break;
- idealNumberOfTicks /= 1.5;
- }
- chart.xAxis.autoComputedTickSizePixels = Math.round(
- this.chart.width / idealNumberOfTicks
- );
- if (chart.xAxis.autoComputedTickSizePixels < 1)
- chart.xAxis.autoComputedTickSizePixels = 1;
- }
- };
-
- /**
- * Sets the candleWidth for the chart. The candleWidth represents the number of horizontal pixels from the start
- * of one bar or candle to the start of the next. This also applies to line charts. It is effectively, the horizontal zoom.
- * The candleWidth can be read from layout.candleWidth.
- *
- * Method also ensures that the new candleWidth is not less than {@link CIQ.ChartEngine.Chart#minimumCandleWidth} and not more than
- * {@link CIQ.ChartEngine.Chart#maximumCandleWidth}. If either of these is the case, candleWidth will be set to whichever value is closer.
- *
- * **Note**: if calling `setCandleWidth()` before `loadChart()`, with a value less than `minimumCandleWidth`, `loadChart()` will reset the candle size to the default candle size (8 pixels).
- *
- * @param {number} newCandleWidth The new candle width. If less than or equal to 0, it will be reset to 8
- * @param {CIQ.ChartEngine.Chart} [chart] Which chart to set the candleWidth. Defaults to the default chart.
- * @memberof CIQ.ChartEngine
- * @example
- * stxx.setCandleWidth(10);
- * stxx.home(); // home() is preferred over draw() in this case to ensure the chart is properly aligned to the right most edge.
-
- */
- CIQ.ChartEngine.prototype.setCandleWidth = function (newCandleWidth, chart) {
- if (!chart) chart = this.chart;
- newCandleWidth = this.constrainCandleWidth(newCandleWidth);
- this.layout.candleWidth = newCandleWidth;
- //chart.maxTicks=Math.ceil(this.chart.width/newCandleWidth+0.5); // we add half of a candle back in because lines and mountains only draw to the middle of the bar
- chart.maxTicks = Math.round(chart.width / newCandleWidth) + 1;
- };
-
- /**
- * Ensures that a candle width value is within the limits of {@link CIQ.ChartEngine#minimumCandleWidth}
- * and {@link CIQ.ChartEngine#maximumCandleWidth}.
- *
- * @param {number} candleWidth The candle width to be checked.
- * @return {number} The value of `candleWidth` if `candleWidth` is between `minimumCandleWidth` and `maximumCandleWith`.
- * Otherwise, `minimumCandleWidth` if `candleWidth` is less than `minimumCandleWidth`. Otherwise, `maximumCandleWith`
- * if `candleWidth` is greater than `maximumCandleWith`.
- * @memberof CIQ.ChartEngine
- * @since 7.4.0
- */
- CIQ.ChartEngine.prototype.constrainCandleWidth = function (candleWidth) {
- var minimumCandleWidth = this.minimumCandleWidth;
- var maximumCandleWidth = this.maximumCandleWidth;
- var animating = this.animations.zoom;
- if (minimumCandleWidth && candleWidth < minimumCandleWidth) {
- candleWidth = minimumCandleWidth;
- if (animating && animating.running) animating.stop();
- }
- if (maximumCandleWidth && candleWidth > maximumCandleWidth) {
- candleWidth = maximumCandleWidth;
- if (animating && animating.running) animating.stop();
- }
- return candleWidth;
- };
-
- /**
- * INJECTABLE
- *
- * Resizes the chart and adjusts the panels. The chart is resized to the size of the container div by calling
- * {@link CIQ.ChartEngine#resizeCanvas}. This method is called automatically if a screen resize event occurs. The charting
- * engine also attempts to detect size changes whenever the mouse is moved. Ideally, if you know the chart is being
- * resized, perhaps because of a dynamic change to the layout of your screen, you should call this method manually.
- * @param {boolean} [maintainScroll=true] By default the scroll position will remain pegged on the right side of the chart. Set this to false to override.
- * @memberof CIQ.ChartEngine
- * @since
- * - 2015-11-1 `resizeChart` now automatically retains scroll position.
- * - 09-2016-19 `resizeChart` now also manages the resizing of the crosshairs.
- */
- CIQ.ChartEngine.prototype.resizeChart = function (maintainScroll) {
- if (this.runPrepend("resizeChart", arguments)) return;
- if (maintainScroll !== false) maintainScroll = true;
- if (maintainScroll) this.preAdjustScroll();
- var previousHeight = this.chart.canvasHeight;
- this.resizeCanvas();
- if (maintainScroll) this.postAdjustScroll();
- if (this.displayInitialized) {
- this.adjustPanelPositions();
- this.draw();
- // This second case occurs if a chart was initialized hidden but now
- // has suddenly been revealed. displayInitialized hadn't been set yet
- // because draw() has never been completed
- } else if (this.chart.canvasHeight !== 0 && previousHeight === 0) {
- this.adjustPanelPositions();
- this.draw();
- }
-
- //redraw the crosshairs to adjust to the new size of the screen.
- this.doDisplayCrosshairs();
- this.updateChartAccessories();
-
- this.runAppend("resizeChart", arguments);
- };
-
- /**
- * Removes any studies from the chart, and hides the chart controls.
- * The chart becomes uninitialized, disabling any interaction with it.
- * The canvas is not cleared; {@link CIQ.clearCanvas} can do that.
- *
- * Useful when a chart is loaded with no data due to a quoteFeed error. Automatically called by {@link CIQ.ChartEngine#loadChart}
- *
- * @memberof CIQ.ChartEngine
- * @since 2016-12-01
- */
- CIQ.ChartEngine.prototype.clear = function () {
- this.displayInitialized = false;
-
- for (var id in this.layout.studies) {
- var sd = this.layout.studies[id];
- CIQ.getFn("Studies.removeStudy")(this, sd);
- }
-
- if (this.controls.chartControls)
- this.controls.chartControls.style.display = "none";
-
- this.chart.panel.title.innerHTML = "";
- this.chart.panel.title.appendChild(
- document.createTextNode(this.chart.panel.display)
- );
- };
-
- /**
- * Adjusts the candleWidth to eliminate left-side gaps on the chart if not enough bars are loaded.
- *
- * Used by the `stretchToFillScreen` parameter of {@link CIQ.ChartEngine#loadChart}
- * @memberof CIQ.ChartEngine
- * @since 4.0.0 This function is now public.
- */
- CIQ.ChartEngine.prototype.fillScreen = function () {
- var chart = this.chart;
- var candleWidth = this.layout.candleWidth;
- var chartWidth = chart.width - this.preferences.whitespace;
- var count = chart.dataSet.length;
-
- if (count * candleWidth >= chartWidth) {
- this.draw();
- return;
- }
-
- // line-type charts go center-to-center in the data point space, so we end up which 1/2 a candle empty on the left and the right..
- //so if we remove a candle from the calculations, we go edge to edge.
- if (!this.mainSeriesRenderer || !this.mainSeriesRenderer.standaloneBars)
- count--;
-
- var newCandleWidth = chartWidth / count;
- this.setCandleWidth(newCandleWidth, chart);
- this.home({ maintainWhitespace: true });
- };
-
- /**
- * Sets the maximimum number of ticks to the requested number. This is effected by changing the candleWidth.
- * See also {@link CIQ.ChartEngine#setCandleWidth}.
- *
- * **Note**: if calling `setMaxTicks()` before `loadChart()`, and the chart will result in a candle width less than `minimumCandleWidth`, `loadChart()` will reset the candle size to the default candle size (8 pixels).
- *
- * @param {number} ticks The number of ticks wide to set the chart.
- * @param {object} [params] Parameters to use with this function.
- * @param {number} params.padding Whitespace in pixels to add to the right of the chart.
- * Setting this field will home the chart to the most recent tick.
- * To home the chart without padding the right side with whitespace, set padding to 0.
- * Omitting the padding field will keep the chart scrolled to the same position.
- * @since 2015-11-1 Added `params` object.
- * @memberof CIQ.ChartEngine
- * @example
- * stxx.setMaxTicks(300);
- * stxx.home(); // home() is preferred over draw() in this case to ensure the chart is properly aligned to the right most edge.
- */
- CIQ.ChartEngine.prototype.setMaxTicks = function (ticks, params) {
- if (!params) params = {};
- ticks = Math.round(ticks);
- if (ticks < 2) ticks = 2;
- var pad = params.padding ? params.padding : 0;
- this.layout.candleWidth = (this.chart.width - pad) / ticks;
- if (!this.layout.candleWidth) this.layout.candleWidth = 8; // Zero candlewidth can only occur if the chart has no width. This might happen if the chart is in a hidden iframe
- this.chart.maxTicks = Math.round(
- this.chart.width / this.layout.candleWidth - 0.499
- );
- if (params.padding || params.padding === 0) this.chart.scroll = ticks + 1; // If padding, then by definition we're homing
- };
-
- /**
- * INJECTABLE
- *
- * This method initializes the chart container events, such as window `resize` events,
- * and the [resizeTimer]{@link CIQ.ChartEngine#setResizeTimer} to ensure the chart adjusts as its container size changes.
- * It also initializes various internal variables, the canvas and creates the chart panel.
- *
- * This is called by {@link CIQ.ChartEngine#loadChart} and should rarely be called directly.
- *
- * Note that the candle width will be reset to 8px if larger than 50px. Even if the value comes from a layout import.
- * This is done to ensure a reasonable candle size is available across devices that may have different screen size.
- *
- * @param {HTMLElement} [container] Node that contains the chart.
- * @memberof CIQ.ChartEngine
- *
- */
- CIQ.ChartEngine.prototype.initializeChart = function (container) {
- if (this.runPrepend("initializeChart", arguments)) return;
- var chart = this.chart;
- if (!chart.symbolObject.symbol) chart.symbolObject.symbol = chart.symbol; // for backwards compatibility so the symbol object is always initialized in case we don't use loadChart()
- if (this.locale) this.setLocale(this.locale);
- if (!this.displayZone && CIQ.ChartEngine.defaultDisplayTimeZone) {
- this.setTimeZone(null, CIQ.ChartEngine.defaultDisplayTimeZone);
- }
- this.resetDynamicYAxis({ noRecalculate: true });
- this.calculateYAxisPositions();
- this.micropixels = 0;
-
- if (container) chart.container = container;
- else container = chart.container;
- container.stx = this;
- if (!container.CIQRegistered) {
- container.CIQRegistered = true;
- CIQ.ChartEngine.registeredContainers.push(container);
- }
- if (this.registerHTMLElements) this.registerHTMLElements(); // Sets all of the internal HTML elements to those in the container
- var canvas = chart.canvas,
- backgroundCanvas = chart.backgroundCanvas,
- tempCanvas = chart.tempCanvas,
- floatCanvas = this.floatCanvas,
- canvasShim = chart.canvasShim;
- if (canvas && document.createElement("canvas").getContext) {
- if (!canvas.id) {
- //Don't play with canvases which have id's since you don't own them
- container.removeChild(canvas);
- chart.canvas = null;
- }
- if (tempCanvas && !tempCanvas.id) {
- container.removeChild(tempCanvas);
- chart.tempCanvas = null;
- }
- if (floatCanvas && !floatCanvas.id) {
- container.removeChild(floatCanvas);
- this.floatCanvas = null;
- }
- if (backgroundCanvas && !backgroundCanvas.id) {
- container.removeChild(backgroundCanvas);
- chart.backgroundCanvas = null;
- }
- } else {
- // Just make sure the candleWidth is sane
- this.setCandleWidth(this.layout.candleWidth);
- }
-
- function styleCanvas(canv, hide) {
- canv.context = canv.getContext("2d");
- canv.context.lineWidth = 1;
- var canvasStyle = canv.style;
- canvasStyle.position = "absolute";
- canvasStyle.left = "0px";
- if (hide) canvasStyle.display = "none";
- }
-
- if (!chart.backgroundCanvas)
- backgroundCanvas = chart.backgroundCanvas = document.createElement(
- "canvas"
- );
- container.appendChild(backgroundCanvas);
- styleCanvas(backgroundCanvas);
-
- if (!chart.canvasShim)
- canvasShim = chart.canvasShim = document.createElement("div");
- canvasShim.className = "stx-canvas-shim";
- container.appendChild(canvasShim);
-
- if (!chart.canvas) canvas = chart.canvas = document.createElement("canvas");
- container.appendChild(canvas);
- styleCanvas(canvas);
- chart.context = canvas.context;
-
- if (!chart.tempCanvas)
- tempCanvas = chart.tempCanvas = document.createElement("canvas");
- container.appendChild(tempCanvas);
- styleCanvas(tempCanvas, true);
-
- if (!this.floatCanvas)
- floatCanvas = this.floatCanvas = document.createElement("canvas");
- container.appendChild(floatCanvas);
- styleCanvas(floatCanvas, true);
-
- this.resizeCanvas();
-
- if (CIQ.isAndroid) {
- tempCanvas.ontouchstart = floatCanvas.ontouchstart = function (e) {
- if (e.preventDefault) e.preventDefault();
- };
- }
-
- var panels = this.panels;
- chart.panel.display = chart.symbol;
- if (chart.symbolDisplay) chart.panel.display = chart.symbolDisplay;
- this.adjustPanelPositions();
- chart.panel = panels[chart.name];
-
- for (var p in panels) {
- var yAxes = panels[p].yaxisLHS.concat(panels[p].yaxisRHS);
- for (var a = 0; a < yAxes.length; a++) {
- yAxes[a].height = panels[p].yAxis.height; // set the [overlay] yAxis height to the panel's main yAxis height...
- this.calculateYAxisMargins(yAxes[a]); // ...so this will work
- }
- }
-
- this.initialWhitespace = this.preferences.whitespace;
- if (chart.dataSet && chart.dataSet.length > 0) {
- chart.scroll = Math.floor(chart.width / this.layout.candleWidth); //chart.maxTicks;
- var wsInTicks = Math.round(
- this.preferences.whitespace / this.layout.candleWidth
- );
- chart.scroll -= wsInTicks;
- }
- if (CIQ.touchDevice) {
- var overlayEdit = container.querySelector(".overlayEdit");
- var overlayTrashCan = container.querySelector(".overlayTrashCan");
- var vectorTrashCan = container.querySelector(".vectorTrashCan");
- var cb = function (self, callRightClick, forceEdit) {
- return function (e) {
- self.deleteHighlighted(callRightClick, forceEdit);
- };
- };
- if (overlayEdit) {
- CIQ.safeClickTouch(overlayEdit, cb(this, true, true));
- if (overlayTrashCan) {
- CIQ.safeClickTouch(overlayTrashCan, cb(this, false));
- }
- } else if (overlayTrashCan) {
- CIQ.safeClickTouch(overlayTrashCan, cb(this, true));
- }
- if (vectorTrashCan) {
- CIQ.safeClickTouch(vectorTrashCan, cb(this, true));
- }
- }
- if (this.manageTouchAndMouse) {
- this.registerTouchAndMouseEvents();
- }
- if (this.handleMouseOut) {
- container.onmouseout = (function (self) {
- return function (e) {
- self.handleMouseOut(e);
- };
- })(this);
- CIQ.smartHover();
- }
-
- if (this.abortDrawings) this.abortDrawings();
- this.undoStamps = [];
- for (var panelName in panels) {
- var panel = panels[panelName];
- if (panel.markerHolder) {
- container.removeChild(panel.markerHolder);
- panel.markerHolder = null;
- }
- }
- for (var i in this.plugins) {
- var plugin = this.plugins[i];
- if (plugin.display) {
- if (plugin.initializeChart) plugin.initializeChart(this);
- }
- }
- // This sets a resize listener for when the screen itself is resized.
- if (!this.resizeListenerInitialized) {
- var self = this;
- this.resizeListenerInitialized = true;
- var resizeListener = function () {
- return function (e) {
- self.resizeChart();
- };
- };
- this.addDomEventListener(window, "resize", resizeListener(), true);
- }
- if (chart.baseline.userLevel) chart.baseline.userLevel = null;
- // This sets the interval timer which checks fore resize condition every X milliseconds (if non zero)
- this.setResizeTimer(this.resizeDetectMS);
- this.runAppend("initializeChart", arguments);
- };
-
- /**
- * Clears out a chart engine instantiated with [new CIQ.ChartEngine()]{@link CIQ.ChartEngine},
- * eliminating all references including the resizeTimer, quoteDriver, styles and eventListeners.
- *
- * It's still up to the developer to set the declared pointer for the instance to null so that the garbage collector can remove it.
- *
- * Please note that **this method will not remove the chart container or any elements within it, even if they were created by the engine**.
- * To do that, execute `stx.container.remove();` to remove the chartContainer DOM elements,
- * and then call this method to remove the chart engine itself. See example.
- *
- *
- * This method should only be used when you no longer need the chart engine and **never** be used in between {@link CIQ.ChartEngine#loadChart} calls to load or change symbols.
- * @memberof CIQ.ChartEngine
- * @example
- * // create
- * var stxx=new CIQ.ChartEngine({container: document.querySelector(".chartContainer")});
- *
- * // execute this line to remove the chart container
- * When using 'tick', please note that this is not a time based display, as such, there is no way to predict what the time for the next tick will be.
- * It can come a second later, a minute later or even more depending on how active a particular instrument may be.
- * If using the future tick functionality ( {@link CIQ.ChartEngine.XAxis#futureTicks} ) when in 'tick' mode, the library uses a pre-defined number ( {@link CIQ.ChartEngine.XAxis#futureTicksInterval} )for deciding what time interval to use for future ticks.
- * See below example on how to override this default.
- *
- * It is important to note that rollups for ‘ticks’ are based on **count** rather than time.
- *
For example: `setPeriodicity({period:5, interval:1, timeUnit:"tick”})` will create a new bar every **5 ticks** rather than every **5 minutes**.
- *
- * Since many ticks can have the exact same timestamp, ticks never get replaced or augmented. As such, if a new tick is provided with a timestamp in the past, even if a record with the exact same date already exists, a new tick will be inserted to the masterData at the proper location rather than one replaced.
- *
- * Lastly, you cannot set an interval for `tick`; as that would not translate into a valid periodicity. If inadvertently set, the engine will "clean it up" (much the same way as if you tried `{period:1, interval:5, timeUnit:"day"}` ).
- *
- * **Note on internal periodicity storage:**
- * The provided parameters will be translated into internal format and stored in the {@link CIQ.ChartEngine#layout} object.
- * Internal format in the layout object **will not match the parameters** used in setPeriodicity.
- *
Use {@link CIQ.ChartEngine#getPeriodicity} to extract internal periodicity into the expected external format.
- *
- * @example
- * // each bar on the screen will represent 15 minutes (combining 15 1-minute bars from your server)
- * stxx.setPeriodicity({period:15, timeUnit:"minute"}, function(err){});
- *
- * @example
- * // each bar on the screen will represent 15 minutes (a single 15 minute bar from your server)
- * stxx.setPeriodicity({period:1, timeUnit:"minute", interval:15}, function(err){});
- *
- * @example
- * // each bar on the screen will represent 30 minutes formed by combining two 15-minute bars; each masterData element represening 15 minutes.
- * stxx.setPeriodicity({period:2, timeUnit:"minute", interval:15}, function(err){});
- *
- * @example
- * // each bar on the screen will represent 1 tick and no particular grouping will be done.
- * stxx.setPeriodicity({period:1, timeUnit:"tick"}, function(err){});
- *
- * @example
- * // each bar on the screen will represent 5 ticks (combining 5 tick objects from your server)
- * stxx.setPeriodicity({period:5, timeUnit:"tick"}, function(err){});
- *
- * @example
- * // each bar on the screen will represent 1 day. MasterData elements will represent one day each.
- * stxx.setPeriodicity({period:1, timeUnit:"day"}, function(err){});
- *
- * @example
- * // this sets the periodicity to 5 minute bars when loadChart is called
- * stxx.loadChart(newSymbol, {
- * // this parameter will cause loadChart to call setSpan with these parameters
- * span: {base: 'day', multiplier: 2},
- * // this parameter will cause loadChart to call setPeriodicity with these parameters
- * periodicity: {period: 1, timeUnit: "minute", interval: 5}
- * }, finishedLoadingChart(stxx.chart.symbol, newSymbol));
- *
- * @example
- * //How to override stxx.chart.xAxis.futureTicksInterval when in 'tick' mode:
- * var stxx=new CIQ.ChartEngine({container:document.querySelector(".chartContainer"), layout:{"candleWidth": 16, "crosshair":true}});
- * stxx.chart.xAxis.futureTicksInterval=1; // to set to 1 minute, for example
- *
- * @param {CIQ.ChartEngine~PeriodicityParameters} params periodicity arguments
- * @param {number} params.period The number of elements from masterData to roll-up together into one data point on the chart (candle,bar, etc). If set to 30 in a candle chart, for example, each candle will represent 30 raw elements of `interval/timeUnit` type.
- * @param {number} [params.interval] Further qualifies pre-rolled details of intra-day `timeUnits` ("millisecond","second","minute") and will be converted to “1” if used with "day", "week" or "month" 'timeUnit'. Some feeds provide data that is already rolled up. For example, there may be a feed that provides 5 minute bars. To let the chart know you want that 5-minute bar from your feed instead of having the chart get individual 1 minute bars and roll them up, you would set the `interval` to '5' and `timeUnit` to 'minute'
- * @param {string} [params.timeUnit] Type of data requested. Valid values are "millisecond","second","minute","day","week", "month" or 'tick'. If not set, will default to "minute". **"hour" is NOT a valid timeUnit. Use `timeUnit:"minute", interval:60` instead**
- * @param {function} [cb] Callback after periodicity is changed. First parameter of callback will be null unless there was an error.
- * @memberof CIQ.ChartEngine
- * @since
- * - 3.0.0 Replaces {@link CIQ.ChartEngine#setPeriodicityV2}.
- * - 4.0.0 Now uses {@link CIQ.ChartEngine#needDifferentData} to determine if new data should be fetched.
- * - 6.3.0 Now only homes chart if new data was fetched.
- * - 8.1.0 Dispatches a "periodicity" event. See also
- * [periodicityEventListener]{@link CIQ.ChartEngine~periodicityEventListener}.
- */
- CIQ.ChartEngine.prototype.setPeriodicity = function (params, cb) {
- if (this.runPrepend("setPeriodicity", arguments)) return;
-
- if (typeof arguments[0] !== "object") {
- params = {
- period: arguments[0],
- interval: arguments[1],
- timeUnit: arguments[2]
- };
- cb = arguments[arguments.length - 1];
- if (arguments.length === 3) params.timeUnit = undefined;
- }
-
- let { period, interval, timeUnit } = params;
- if (typeof cb !== "function") cb = null;
-
- ({ period, interval, timeUnit } = CIQ.cleanPeriodicity(
- period,
- interval,
- timeUnit
- ));
-
- let { layout } = this;
- layout.setSpan = {}; // No longer in a span if we've set a specific periodicity
- layout.range = {}; // No longer in a range if we've set a specific periodicity
-
- this.chart.inflectionPoint = null; // reset where the consolidation occurs from
- let getDifferentData = false;
-
- if (this.chart.symbol) {
- getDifferentData = this.needDifferentData({
- period: period,
- interval: interval,
- timeUnit: timeUnit
- });
- }
-
- let {
- candleWidth: cw,
- periodicity: prvPeriodicity,
- interval: prvInterval,
- timeUnit: prvTimeUnit
- } = layout;
- let prevPeriodicity = { prvPeriodicity, prvInterval, prvTimeUnit };
-
- layout.periodicity = period;
- layout.interval = interval;
- layout.timeUnit = timeUnit;
-
- const self = this;
- let dispatchData = {
- stx: self,
- differentData: getDifferentData,
- prevPeriodicity
- };
- function onComplete() {
- self.dispatch("periodicity", dispatchData);
- if (cb) cb(null);
- }
-
- if (getDifferentData) {
- this.changeOccurred("layout");
- this.clearCurrentMarketData();
- if (this.quoteDriver) {
- for (let c in this.charts) {
- let thisChart = this.charts[c];
- if (thisChart.symbol) {
- if (this.displayInitialized) {
- this.quoteDriver.newChart(
- {
- symbol: thisChart.symbol,
- symbolObject: thisChart.symbolObject,
- chart: thisChart
- },
- onComplete
- );
- } else {
- this.loadChart(thisChart.symbol, { chart: thisChart }, onComplete);
- }
- }
- }
- } else if (this.dataCallback) {
- this.dataCallback();
- onComplete();
- } else {
- console.log(
- "cannot change periodicity because neither dataCallback or quoteDriver are set"
- );
- }
- this.home();
- return;
- }
-
- for (let chartName in this.charts) {
- let chart = this.charts[chartName];
- let { dataSegment, dataSet, maxTicks, scroll } = chart;
- let dataSegmentLength = dataSegment ? dataSegment.length : 0,
- dataSetLength = dataSet ? dataSet.length : 0;
- let dt;
- let pos = Math.round(chart.maxTicks / 2);
- this.setCandleWidth(cw, chart);
- let centerMe = true,
- rightAligned = false;
- if (scroll <= maxTicks)
- // don't attempt to center the chart if we're scrolled into the future
- centerMe = false;
- else if (dataSegment && !dataSegment[pos]) {
- // don't attempt to center the chart if we're scrolled into the past
- centerMe = false;
- rightAligned = scroll - dataSetLength; // We'll use this to keep the same amount of right alignment
- }
-
- if (centerMe && dataSegmentLength > 0) {
- if (maxTicks < (Math.round(this.chart.width / cw - 0.499) - 1) / 2) {
- pos = dataSegmentLength - 1;
- }
- if (pos >= dataSegmentLength) {
- dt = dataSegment[dataSegmentLength - 1].DT;
- pos = dataSegmentLength - 1;
- } else {
- dt = dataSegment[pos].DT;
- }
- }
-
- this.createDataSet();
-
- if (centerMe) {
- // If we're scrolled somewhere into the middle of the chart then we will keep the chart centered as we increase or decrease periodicity
- if (dataSegmentLength > 0) {
- for (let i = dataSetLength - 1; i >= 0; i--) {
- let nd = dataSet[i].DT;
- if (nd.getTime() < dt.getTime()) {
- chart.scroll = dataSetLength - 1 - i + pos;
- break;
- }
- }
- }
- } else if (!rightAligned) {
- let wsInTicks = Math.round(this.preferences.whitespace / cw);
- chart.scroll = maxTicks - wsInTicks - 1; // Maintain the same amount of left alignment
- } else {
- chart.scroll = dataSet.length + rightAligned; // Maintain the same amount of right alignment
- }
- }
-
- if (this.displayInitialized) this.draw();
- this.changeOccurred("layout");
-
- if (this.quoteDriver) {
- for (let chartName in this.charts) {
- let chart = this.charts[chartName];
- if (chart.symbol && (chart.moreAvailable || !chart.upToDate)) {
- this.quoteDriver.checkLoadMore(chart);
- }
- }
- }
- //this.home(); // let centerMe do its thing
- onComplete();
- this.runAppend("setPeriodicity", arguments);
- };
-
- /**
- * Returns true if the chart needs new data to conform with the new periodicity.
- * @param {object} newPeriodicity newPeriodicity. See {@link CIQ.ChartEngine#setPeriodicity}
- * @param {number} newPeriodicity.period `period` as required by {@link CIQ.ChartEngine#setPeriodicity}
- * @param {string} newPeriodicity.interval `interval` as required by {@link CIQ.ChartEngine#setPeriodicity}
- * @param {string} newPeriodicity.timeUnit `timeUnit` as required by {@link CIQ.ChartEngine#setPeriodicity}
- * @return {boolean} True if the cart needs data in a new periodicity
- * @memberof CIQ.ChartEngine
- * @since 4.0.0
- */
- CIQ.ChartEngine.prototype.needDifferentData = function (newPeriodicity) {
- var layout = this.layout;
- var isDaily = CIQ.ChartEngine.isDailyInterval(newPeriodicity.interval),
- wasDaily = CIQ.ChartEngine.isDailyInterval(layout.interval);
- var getDifferentData = false;
-
- if (this.dontRoll || !wasDaily) {
- // we are not rolling so monthly and weekly are not the same as daily or any of the intraday... so simply check for different interval.
- if (layout.interval != newPeriodicity.interval) getDifferentData = true;
- } else {
- //we are rolling weeekly and monthly and wasn't intraday mode...so check to see if we an still use daily data for the new periodicity
- if (isDaily != wasDaily) getDifferentData = true;
- }
-
- // safety check to deal with defaults.
- if (!isDaily && !newPeriodicity.timeUnit) newPeriodicity.timeUnit = "minute";
- if (!wasDaily && !layout.timeUnit) layout.timeUnit = "minute";
-
- if (newPeriodicity.timeUnit != layout.timeUnit) getDifferentData = true; // !!! Do not change to !==
-
- if (!this.masterData || !this.masterData.length) getDifferentData = true; // always fetch if no data
-
- return getDifferentData;
- };
-
- /**
- * Returns the current periodicity of the chart in the format required by
- * {@link CIQ.ChartEngine#setPeriodicity}.
- *
- * @returns {CIQ.ChartEngine~PeriodicityParameters} An object literal containing the properties
- * that define the periodicity: `period`, `interval`, and `timeUnit`; for example,
- * `{period: 2, interval: 5, timeUnit: "minute"}`.
- *
- * @memberof CIQ.ChartEngine
- * @since 7.5.0
- *
- * @see [Periodicity Tutorial]{@tutorial Periodicity}
- */
- CIQ.ChartEngine.prototype.getPeriodicity = function () {
- var layout = this.layout;
- var interval = layout.interval,
- timeUnit = layout.timeUnit;
-
- if (!timeUnit) {
- timeUnit = interval;
- interval = 1;
- }
-
- return { period: layout.periodicity, interval: interval, timeUnit: timeUnit };
- };
-
- };
-
-
- let __js_core_engine_record_ = (_exports) => {
-
-
- var CIQ = _exports.CIQ;
-
- /**
- * Based on the standardMarketIterator and the last entry of masterData, determines whether the chart contains data up till the current iterators next tick.
- *
- * For efficiency once {@link CIQ.ChartEngine.isHistoricalMode} is set to false, this will always return false.
- * @return {boolean} True if viewing historical mode
- * @since 6.0.0
- * @private
- */
- CIQ.ChartEngine.prototype.isHistoricalMode = function () {
- var dateNow = new Date(),
- historic = true,
- masterData = this.masterData;
- if (!this.isHistoricalModeSet) {
- return false;
- }
- if (masterData.length) {
- var lastDate = this.getFirstLastDataRecord(masterData, "DT", true);
- var iter = this.standardMarketIterator(lastDate.DT);
- historic = (iter ? iter.next() : lastDate.DT) <= dateNow;
-
- // special case: daily chart, market has not opened yet today
- // historic would always be set even though we have all the data
- if (historic && CIQ.ChartEngine.isDailyInterval(iter.interval)) {
- var open = this.chart.market.getOpen();
- if (open && dateNow < open) {
- dateNow.setHours(0, 0, 0, 0);
- if (+dateNow == +iter.begin) historic = false;
- }
- }
- }
- return historic;
- };
-
- /**
- * Whether the chart is scrolled to a home position.
- *
- * @returns {boolean} true when the scroll position shows the last tick of the dataSet
- * @memberof CIQ.ChartEngine
- * @since 2016-06-21
- */
- CIQ.ChartEngine.prototype.isHome = function () {
- var chart = this.chart,
- dataSet = chart.dataSet,
- animating = chart.animatingHorizontalScroll;
- return (
- this.pixelFromTick(dataSet.length - (animating ? 2 : 1), chart) <
- chart.width + chart.panel.left
- );
- //return ((this.chart.scroll-1)*this.layout.candleWidth)+this.micropixels<=this.chart.width+1;
- };
-
- /**
- * Finds the previous element before dataSegment[bar] in the dataSet which has data for field
- * @param {CIQ.ChartEngine.Chart} chart An instance of {@link CIQ.ChartEngine.Chart}
- * @param {string} field The field to check for data
- * @param {number} bar The index into the dataSegment
- * @return {object} dataSet element which has data
- * @memberof CIQ.ChartEngine
- * @since 4.0.0
- */
- CIQ.ChartEngine.prototype.getPreviousBar = function (chart, field, bar) {
- return this.getNextBarInternal(chart, field, bar, -1);
- };
-
- /**
- * Finds the next element after dataSegment[bar] in the dataSet which has data for field
- * @param {CIQ.ChartEngine.Chart} chart An instance of {@link CIQ.ChartEngine.Chart}
- * @param {string} field The field to check for data
- * @param {number} bar The index into the dataSegment
- * @return {object} dataSet element which has data
- * @memberof CIQ.ChartEngine
- * @since 4.0.0
- */
- CIQ.ChartEngine.prototype.getNextBar = function (chart, field, bar) {
- return this.getNextBarInternal(chart, field, bar, 1);
- };
-
- /**
- * @param {CIQ.ChartEngine.Chart} chart An instance of {@link CIQ.ChartEngine.Chart}
- * @param {string} field The field to check for data
- * @param {number} bar The index into the dataSegment
- * @param {number} direction 1 or -1, for next or previous
- * @return {object} dataSet element which has data
- * @memberof CIQ.ChartEngine
- * @since 4.0.0
- * @private
- */
- CIQ.ChartEngine.prototype.getNextBarInternal = function (
- chart,
- field,
- bar,
- direction
- ) {
- var seg = chart.dataSegment && chart.dataSegment[bar];
- if (seg) {
- var tick = seg.tick;
- while (tick > 0 && tick < chart.dataSet.length) {
- tick = tick + direction;
- var ds = chart.dataSet[tick];
- if (ds) {
- var tuple = CIQ.existsInObjectChain(ds, field);
- if (tuple && tuple.obj[tuple.member]) return ds;
- }
- }
- }
- return null;
- };
-
- /**
- * Returns the first or last record in a quotes array (e.g. masterData, dataSet) containing the requested field.
- * If no record is found, will return null
- * @param {array} data quotes array in which to search
- * @param {string} field field to search for
- * @param {boolean} [last] Switch to reverse direction; default is to find the first record. Set to true to find the last record.
- * @return {object} The found record, or null if not found
- * @memberof CIQ.ChartEngine
- * @since 5.2.0
- */
- CIQ.ChartEngine.prototype.getFirstLastDataRecord = function (
- data,
- field,
- last
- ) {
- if (data && data.length) {
- var c = last ? data.length - 1 : 0;
- while (c >= 0 && c < data.length) {
- if (data[c] && typeof data[c][field] != "undefined") {
- return data[c];
- }
- if (last) c--;
- else c++;
- }
- }
- return null;
- };
-
- /**
- * Returns the tick position of the leftmost position on the chart.
- * @return {number} The tick for the leftmost position
- * @memberof CIQ.ChartEngine
- */
- CIQ.ChartEngine.prototype.leftTick = function () {
- return this.chart.dataSet.length - this.chart.scroll;
- };
-
- /**
- * Convenience function returns the next or previous interval from the provided date-time at the current chart's periodicity.
- * See {@link CIQ.Market} and {@link CIQ.Market.Iterator} for more details.
- *
- * For 'tick' intervals, since there is no predictable periodicity, the next interval will be determined by {@link CIQ.ChartEngine.XAxis#futureTicksInterval}
- * @param {date} DT A JavaScript Date representing the base time for the request in {@link CIQ.ChartEngine#dataZone} timezone.
- * @param {number} [period] The number of periods to jump. Defaults to 1. Can be negative to go back in time.
- * @param {boolean} [useDataZone=true] By default the next interval will be returned in {@link CIQ.ChartEngine#dataZone}. Set to false to receive a date in {@link CIQ.ChartEngine#displayZone} instead.
- * @return {date} The next interval date
- * @memberof CIQ.ChartEngine
- */
- CIQ.ChartEngine.prototype.getNextInterval = function (DT, period, useDataZone) {
- if (!period) period = 1;
- if (useDataZone !== false) useDataZone = true;
-
- var iter = this.standardMarketIterator(
- DT,
- useDataZone ? this.dataZone : this.displayZone
- );
- if (!iter) return DT; // cannot find so just return input date
- if (period < 1) {
- return iter.previous(period * -1);
- }
- return iter.next(period);
- };
-
- /**
- * Convenience function returns a new market iterator at the current chart's periodicity.
- * For 'tick' intervals, since there is no predictable periodicity, the iterator interval will be determined by {@link CIQ.ChartEngine.XAxis#futureTicksInterval}
- * See {@link CIQ.Market} and {@link CIQ.Market.Iterator} for more details.
- * @param {date} begin A JavaScript Date representing the iterator begin date in {@link CIQ.ChartEngine#dataZone} timezone. See {@link CIQ.Market#newIterator} for details.
- * @param {string} [outZone] A valid timezone from the timeZoneData.js library. This should represent the time zone for the returned date. Defaults {@link CIQ.ChartEngine#dataZone}. See {@link CIQ.Market#newIterator} for details.
- * @param {CIQ.ChartEngine.Chart} [chart] The chart object.
- * @return {object} A new iterator.
- * @memberof CIQ.ChartEngine
- */
- CIQ.ChartEngine.prototype.standardMarketIterator = function (
- begin,
- outZone,
- chart
- ) {
- var cht = chart || this.chart;
- if (!cht.market) return null;
- var iter_parms = {
- begin: begin,
- interval: this.layout.interval,
- periodicity:
- this.layout.interval == "tick"
- ? this.chart.xAxis.futureTicksInterval
- : this.layout.periodicity,
- timeUnit: this.layout.timeUnit,
- outZone: outZone
- };
- return cht.market.newIterator(iter_parms);
- };
-
- };
-
-
- let __js_core_engine_render_ = (_exports) => {
-
-
- if (!_exports.SplinePlotter) _exports.SplinePlotter = {};
- var CIQ = _exports.CIQ,
- splinePlotter = _exports.SplinePlotter;
-
- /**
- * INJECTABLE
- * Animation Loop
- *
- * This is the main rendering function in the animation loop. It draws the chart including panels, axis, and drawings.
- * This method is called continually as a user pans or zooms the chart.
- * This would be a typical place to put an injection to add behavior to the chart after a drawing operation is complete.
- * @memberof CIQ.ChartEngine
- */
- CIQ.ChartEngine.prototype.draw = function () {
- this.debug();
- var chart = this.chart,
- layout = this.layout;
- if (!chart.canvas) return;
- if (!chart.dataSet) return;
- if (!chart.canvasHeight) return;
- //if(!this.useAnimation && new Date()-this.grossDragging<500) return;
-
- this.offset = (layout.candleWidth * this.candleWidthPercent) / 2;
- CIQ.clearCanvas(chart.canvas, this);
- if (!this.masterData) return;
-
- if (this.runPrepend("draw", arguments)) return;
- if (!this.defaultColor) this.getDefaultColor();
-
- this.vectorsShowing = false;
-
- this.drawPanels();
- this.yAxisLabels = [];
- var i, plugin;
-
- this.correctIfOffEdge();
- this.createDataSegment();
- this.setBaselines(chart);
- var axisRepresentation = this.createXAxis(chart);
- this.initializeDisplay(chart);
- this.drawXAxis(chart, axisRepresentation);
- try {
- this.renderYAxis(chart);
- } catch (e) {
- if (e && e.message === "reboot draw") {
- return this.draw();
- }
- throw e;
- }
-
- /// Calculate tmpWidth which represents the amount of width that the candle takes, slightly less than candleWidth
- chart.tmpWidth = Math.floor(layout.candleWidth * this.candleWidthPercent); // So we don't need to compute it a thousand times for every candle
- if (chart.tmpWidth % 2 === 0) {
- // assure that candles are always odd number of pixels wide
- chart.tmpWidth += 1;
- if (chart.tmpWidth > layout.candleWidth)
- // If there isn't space then reduce further
- chart.tmpWidth -= 2;
- }
- if (chart.tmpWidth < 0.5) chart.tmpWidth = 0.5;
-
- for (i in this.plugins) {
- plugin = this.plugins[i];
- if (plugin.display) {
- if (plugin.drawUnder) plugin.drawUnder(this, chart);
- }
- }
-
- if (chart.legend) chart.legend.colorMap = null;
- if (this.controls.baselineHandle)
- this.controls.baselineHandle.style.display = "none";
-
- this.rendererAction(chart, "underlay");
- CIQ.getFn("Studies.displayStudies")(this, chart, true);
- this.displayChart(chart);
- CIQ.getFn("Studies.displayStudies")(this, chart, false);
- this.rendererAction(chart, "overlay");
-
- if (chart.legend && chart.legend.colorMap && chart.legendRenderer) {
- chart.legendRenderer(this, {
- chart: chart,
- legendColorMap: chart.legend.colorMap,
- coordinates: {
- x: chart.legend.x,
- y: chart.legend.y + chart.panel.yAxis.top
- }
- });
- }
-
- for (i in this.plugins) {
- plugin = this.plugins[i];
- if (plugin.display) {
- if (plugin.drawOver) plugin.drawOver(this, chart);
- }
- }
-
- // Do this after all the drawing has taken place. That way the y-axis text sits on top of anything that
- // has been drawn underneath. For instance, if panel.yaxisCalculatedPaddingRight>0 and the y-axis sits on top of the chart
- for (var panel in this.panels) {
- if (!this.panels[panel].hidden) this.plotYAxisText(this.panels[panel]);
- }
- for (var yLbl = 0; yLbl < this.yAxisLabels.length; yLbl++) {
- var labelParams = this.yAxisLabels[yLbl];
- if (
- labelParams.src == "series" &&
- labelParams.args[6] &&
- labelParams.args[6].drawSeriesPriceLabels === false
- )
- continue;
- this.createYAxisLabel.apply(this, labelParams.args);
- }
- if (this.createCrosshairs) this.createCrosshairs();
- if (this.drawVectors) this.drawVectors();
- this.drawCurrentHR();
- this.displayInitialized = true;
- var controls = this.controls;
- if (controls) {
- var showControls =
- this.manageTouchAndMouse &&
- (!this.mainSeriesRenderer || !this.mainSeriesRenderer.nonInteractive);
- if (controls.home)
- controls.home.style.display =
- showControls && !this.isHome() ? "block" : "none";
- if (controls.chartControls)
- controls.chartControls.style.display = showControls ? "block" : "none";
- }
- if (CIQ.Marker) this.positionMarkers();
- if (this.quoteDriver && this.animations.zoom.hasCompleted) {
- this.quoteDriver.checkLoadMore(chart);
- }
- this.runAppend("draw", arguments);
- this.makeAsyncCallbacks();
- };
-
- /**
- * Adds a series renderer to the chart. A series renderer manages a group of series that are
- * rendered on the chart in the same manner. For instance, several series which are part of the
- * same stacked histogram:
- *
- *
- *
- * You must manage the persistency of a renderer and remove individual series
- * ({@link CIQ.Renderer#removeSeries}), remove all series ({@link CIQ.Renderer#removeAllSeries}),
- * or even delete the renderer ({@link CIQ.ChartEngine#removeSeriesRenderer}) as needed by your
- * application.
- *
- * **Note:** Once a renderer is set for a chart, it remains loaded with its series definitions
- * and y-axis (if one is used) even if a new symbol is loaded. Calling `setSeriesRenderer` again
- * with the same renderer name just returns the previously created renderer. **Be careful not to
- * send a different y‑axis object unless you have deleted the previous one by completely
- * removing all of its associated series** (see {@link CIQ.Renderer#removeAllSeries}). Failure to
- * do this will cause multiple axes to be displayed, causing the original one to become orphaned.
- *
- * @param {CIQ.Renderer} renderer The series renderer to add to the chart.
- * @return {CIQ.Renderer} The renderer added to the chart by this function or, if the chart
- * already has a renderer of the same name, a reference to that renderer.
- *
- * @memberof CIQ.ChartEngine
- * @since 07/01/2015
- *
- * @see {@link CIQ.Renderer}
- * @see {@link CIQ.ChartEngine#removeSeriesRenderer} for release functionality
- * @see {@link CIQ.ChartEngine#addSeries} for additional implementation examples
- *
- * @example
- * // Group the series together and select "line" as the rendering type to display the series.
- * const mdataRenderer = stxx
- * .setSeriesRenderer(
- * new CIQ.Renderer.Lines({
- * params: {
- * name: "My Line Series",
- * type: "line",
- * width: 4,
- * callback: mdataLegend
- * }
- * })
- * )
- * .removeAllSeries()
- * .attachSeries(symbol1, { color: "red", permanent: true })
- * .attachSeries(symbol2, "blue")
- * .attachSeries(symbol3, "yellow")
- * .ready()
- */
- CIQ.ChartEngine.prototype.setSeriesRenderer = function (renderer) {
- const { baseline, name, panel, yAxis } = renderer.params;
- if (this.chart.seriesRenderers[name]) {
- return this.chart.seriesRenderers[name]; // renderer already created
- }
-
- if (yAxis) {
- renderer.params.yAxis = this.addYAxis(this.panels[panel], yAxis);
- this.resizeChart();
- }
- renderer.stx = this;
-
- this.chart.seriesRenderers[name] = renderer;
-
- if (baseline) this.registerBaselineToHelper(renderer);
-
- return renderer;
- };
-
- /** Sets a renderer for the main chart. This is done by parsing the layout.chartType and layout.aggregationType and creating the renderer which will support those settings.
- * @param {boolean} eraseData Set to true to erase any existing series data
- * @memberOf CIQ.ChartEngine
- * @since 5.1.0
- */
- CIQ.ChartEngine.prototype.setMainSeriesRenderer = function (eraseData) {
- let { chartType, aggregationType } = this.layout;
- const { chart } = this;
- const { custom } = chart;
- let r = this.mainSeriesRenderer;
-
- let displayInitialized = this.displayInitialized;
- if (r) {
- if (eraseData) this.setMasterData();
- this.displayInitialized = false; // prevent redraws while series is not attached to main renderer
- r.removeAllSeries();
- this.removeSeriesRenderer(r);
- r = this.mainSeriesRenderer = null;
- }
-
- if (custom && custom.chartType) chartType = custom.chartType;
- if (chartType == "none") return; // no renderer and no default lines renderer
- if (aggregationType && aggregationType != "ohlc") chartType = aggregationType;
- const renderer = CIQ.Renderer.produce(chartType, {
- panel: chart.panel.name,
- name: "_main_series",
- highlightable: false,
- useChartLegend: true
- });
- if (renderer) {
- this.setSeriesRenderer(renderer).attachSeries(null, {
- display: chart.symbol
- });
- r = this.mainSeriesRenderer = renderer;
- }
-
- this.displayInitialized = displayInitialized;
- // Convenience access
- ["highLowBars", "standaloneBars", "barsHaveWidth"].forEach(
- function (p) {
- chart[p] = this.mainSeriesRenderer && this.mainSeriesRenderer[p];
- }.bind(this)
- );
- };
-
- /**
- * Detaches a series renderer from the chart and deletes its associated y-axis if no longer used by any other renderer.
- *
- * Note: the actual series and related data are not deleted with this command and can be attached or continue to be used with other renderers.
- *
- * Note: the actual renderer (created by using new `CIQ.Renderer.xxxxx`) is not deleted but simply detached from the chart. You can re-attach it again if needed.
- * To delete the renderer use `delete myRenderer`. See example in {@link CIQ.Renderer.Lines}
- *
- * @param {object} renderer The actual renderer instance to be removed
- * @memberof CIQ.ChartEngine
- * @since 07/01/2015
- */
- CIQ.ChartEngine.prototype.removeSeriesRenderer = function (renderer) {
- const { baseline, name } = renderer.params;
- const handle = this.controls[`${name} baseline-handle`];
- if (baseline) {
- this.removeBaselineFromHelper(renderer);
- if (handle) {
- this.container.removeChild(handle);
- delete this.controls[handle];
- }
- }
- delete this.chart.seriesRenderers[name];
- };
-
- /**
- * Retrieves a series renderer from the chart
- * @param {string} name Handle to access the renderer (params.name)
- * @return {object} the matching series renderer if found
- * @memberof CIQ.ChartEngine
- * @since 07/01/2015
- */
- CIQ.ChartEngine.prototype.getSeriesRenderer = function (name) {
- return this.chart.seriesRenderers[name];
- };
-
- /**
- * Returns the first renderer found that contains a series, or null if not found.
- *
- * @param {string} seriesId ID of the series to find.
- * @return {object} The matching series renderer if found.
- * @memberof CIQ.ChartEngine
- * @since 7.3.0
- */
- CIQ.ChartEngine.prototype.getRendererFromSeries = function (seriesId) {
- var renderers = this.chart.seriesRenderers;
- for (var r in renderers) {
- for (var s in renderers[r].seriesParams) {
- if (renderers[r].seriesParams[s].id == seriesId) return renderers[r];
- }
- }
- return null;
- };
-
- /**
- * Initializes boundary clipping on the requested panel. Use this when you are drawing on the canvas and wish for the
- * drawing to be contained within the panel. You must call {@link CIQ.ChartEngine#endClip} when your drawing functions are complete.
- * @param {string} [panelName] The name of the panel. Defaults to the chart itself.
- * @param {boolean} [allowYAxis=false] If true then the clipping region will include the y-axis. By default the clipping region ends at the y-axis.
- * @memberof CIQ.ChartEngine
- */
- CIQ.ChartEngine.prototype.startClip = function (panelName, allowYAxis) {
- if (!panelName) panelName = this.chart.panel.name;
- var panel = this.panels[panelName];
- var yAxis = panel.yAxis;
- var chart = this.chart;
- chart.context.save();
- chart.context.beginPath();
- var left = panel.left;
- var width = panel.width;
- if (allowYAxis) {
- left = 0;
- width = this.width;
- } else if (panel.yaxisLHS && panel.yaxisLHS.length) {
- left++;
- width--;
- }
- chart.context.rect(left, yAxis.top, width, yAxis.height);
- chart.context.clip();
- };
-
- /**
- * Completes a bounded clipping operation. See {@link CIQ.ChartEngine#startClip}.
- * @memberof CIQ.ChartEngine
- */
- CIQ.ChartEngine.prototype.endClip = function () {
- this.chart.context.restore();
- };
-
- /**
- * Sets the line style for the main chart.
- *
- * Applies to the {@link CIQ.Renderer.Lines} renderer only.
- *
- * @param {object|string} [obj] Parameters object or color string (see `obj.color`).
- * @param {string} [obj.color] A color to use for the line plot. Must be an RGB, RGBA, or three-
- * or six‑digit hexadecimal color number or
- *
- * CSS color keyword; for example, "rgb(0, 255, 0)", "rgba(0, 255, 0, 0.5),
- * "#0f0", "#00FF00", or "green". Alternatively, `obj` can be set to a color string directly
- * if no other parameters are needed.
- * @param {number[]|string} [obj.pattern] Pattern to use as an alternative to a solid line for the
- * line plot. Valid string values are "solid", "dotted" and "dashed". Arrays specify the
- * sequence of drawn pixels and blank pixels as alternating elements starting at index 0; for
- * example, [1, 2, 3, 2] specifies a line containing one drawn pixel followed by two blank
- * pixels followed by three drawn pixels followed by two more blank pixels, then the pattern
- * repeats.
- * @param {number} [obj.width] Width of the line plot.
- * @param {string} [obj.baseColor] Color to use for the base of a mountain chart. Must be an RGB,
- * RGBA, or three- or six‑digit hexadecimal color number or CSS color keyword (see
- * `obj.color`).
- * @param {CIQ.ChartEngine.Chart|CIQ.Studies.StudyDescriptor} [target=this.chart] Target to which
- * the line style is attached.
- *
- * @memberof CIQ.ChartEngine
- * @since
- * - 4.0.0
- * - 8.2.0 Added `obj.baseColor` parameter.
- *
- * @example
- *
- *
- * **Returns:**
- * - Formatted text label for the particular date passed in.
- *
- * @type function
- * @memberof CIQ.ChartEngine.XAxis#
- * @example
- * stxx.chart.xAxis.formatter = function(labelDate, gridType, timeUnit, timeUnitMultiplier, defaultText){
- * // Your code here to format your string.
- * // Example: always return HH:MM regardless of gridType,
- * // even if gridType is a 'boundary' that normally would display
- * // a date in intraday periodicity or a month in daily periodicity
- *
- * //You can always return back 'defaultText' if you do not wish to customize the particular value.
- *
- * var stringDate = labelDate.getHours() + ':' + labelDate.getMinutes();
- * return stringDate;
- * }
- * @example
- * stxx.chart.xAxis.formatter = function(labelDate, gridType, timeUnit, timeUnitMultiplier, defaultText){
- * // Your code here to format your string.
- * // Example: return HH:MM when gridType is "line" otherwise returned the default text.
- *
- * if( gridType == "line" ) {
- * var stringDate = labelDate.getHours() + ':' + labelDate.getMinutes();
- * return stringDate;
- * else
- * return defaultText;
- * }
- * @since
- * - 3.0.0 Using x axis formatter now is available for year and month boundaries.
- * - 6.3.0 Added `defaultText` parameter.
- * - 6.3.0 Added drawing type as possible `gridType` value.
- */
- formatter: null,
- /**
- * If true, the user selected (default browser if none selected) timezone will be used on the x axis.
- * If not set to true, the data timezone will be used even if a user timezone was set.
- * @type boolean
- * @default
- * @memberof CIQ.ChartEngine.XAxis#
- */
- adjustTimeZone: true,
- /**
- * Ideal space between x-axis labels in pixels.
- * If null then the chart will attempt a tick size and time unit in proportion to the chart.
- * Please note that if `stxx.chart.yAxis.goldenRatioYAxis` is set to `true`, this setting will also affect the spacing between y-axis labels.
- * Please note that this setting will be overwritten at rendering time if too small to prevent labels from covering each other.
- * Not applicable if {@link CIQ.ChartEngine.XAxis#timeUnit} is manually set.
- * See {@tutorial Custom X-axis} for additional details.
- * @type number
- * @default
- * @memberof CIQ.ChartEngine.XAxis#
- */
- idealTickSizePixels: null,
- /**
- * Overrides default used in {@link CIQ.ChartEngine#createTickXAxisWithDates}
- *
- * Name Type Description
- * labelDate Date javaScript date to format
- * gridType String "boundary", "line", or name of drawing (e.g. "vertical") for the axis labels.
Absent for the floating crosshair label
- * timeUnit Enumerated type CIQ.MILLISECOND
CIQ.SECOND
CIQ.MINUTE
CIQ.HOUR
CIQ.DAY
CIQ.MONTH
CIQ.YEAR
CIQ.DECADE
Absent for the floating crosshair label.
- * timeUnitMultiplier Number How many timeUnits.
Absent for the floating crosshair label.
- * defaultText String Contains the default date label that would be used if no formatter is defined. Simply return this value for dates where no formatting is desired.
Allowable values:
- * - CIQ.MILLISECOND,
- * - CIQ.SECOND
- * - CIQ.MINUTE
- * - CIQ.HOUR
- * - CIQ.DAY
- * - CIQ.WEEK
- * - CIQ.MONTH
- * - CIQ.YEAR
- * - CIQ.DECADE
- *
- * Visual Reference for sample code below (draw a label every 5 seconds using 1 second periodicity ) :
- * ![xAxis.timeUnit](xAxis.timeUnit.png "xAxis.timeUnit")
- * @type number
- * @default
- * @memberof CIQ.ChartEngine.XAxis#
- * @example
- * // The following will cause the default implementation of createTickXAxisWithDates to print labels in seconds every 5 seconds.
- * // masterData is in 1 second intervals for this particular example.
- * stxx.chart.xAxis.timeUnit = CIQ.SECOND;
- * stxx.chart.xAxis.timeUnitMultiplier = 5;
- */
- timeUnit: null,
- /**
- * Overrides default used in {@link CIQ.ChartEngine#createTickXAxisWithDates}
- * @type number
- * @default
- * @memberof CIQ.ChartEngine.XAxis#
- * @example
- * // The following will cause the default implementation of createTickXAxisWithDates to print labels in seconds every 5 seconds.
- * // masterData is in 1 second intervals for this particular example.
- * stxx.chart.xAxis.timeUnit = CIQ.SECOND;
- * stxx.chart.xAxis.timeUnitMultiplier = 5;
- */
- timeUnitMultiplier: null,
- /**
- * Set to true to draw a line above the x-axis.
- * @type boolean
- * @default
- * @memberof CIQ.ChartEngine.XAxis#
- */
- displayBorder: true,
- /**
- * Set to false to suppress grid lines
- * @type boolean
- * @default
- * @memberof CIQ.ChartEngine.XAxis#
- */
- displayGridLines: true,
- /**
- * Switch to temporarily hide the x-axis. Set to `true' to activate.
- *
- * Axis space will be maintained. To completely remove the x axis, including spacing use {@link CIQ.ChartEngine#xaxisHeight}
- * @type boolean
- * @default
- * @memberof CIQ.ChartEngine.XAxis#
- * @since 3.0.0
- */
- noDraw: null,
- /**
- * Minimum size for label. This ensures adequate padding so that labels don't collide with one another.
- * Please note that this setting is used during the rendering process, not during the label spacing calculation process and will be overwritten if too small to prevent labels from covering each other.
- * To modify at what interval labels will be placed, please see {@tutorial Custom X-axis} for more details
- * @type number
- * @default
- * @memberof CIQ.ChartEngine.XAxis#
- */
- minimumLabelWidth: 50,
- /**
- * Set to false to hide axis markings in the future.
- * @type boolean
- * @default
- * @memberof CIQ.ChartEngine.XAxis#
- */
- futureTicks: true,
- /**
- * Set to the number of minutes ticks will move by when iterating in "tick" interval.
- *
- * Use css styles `stx_xaxis_dark` to control **color only** for the divider dates.
- * Use css styles `stx_grid_border`, `stx_grid` and `stx_grid_dark` to control the grid line colors.
- * The dark styles are used for dividers; when the grid changes to a major point such as the start of a new day on an intraday chart, or a new month on a daily chart.
- *
- * See {@tutorial Custom X-axis} and {@tutorial CSS Overview} for additional details.
- *
- * @param {CIQ.ChartEngine.Chart} chart The chart to create an x-axis for
- * @return {CIQ.ChartEngine.XAxisLabel[]} axisRepresentation that can be passed in to {@link CIQ.ChartEngine#drawXAxis}
- * @memberof CIQ.ChartEngine.AdvancedInjectable#
- * @alias createXAxis
- *
- */
- CIQ.ChartEngine.prototype.createXAxis = function (chart) {
- if (chart.dataSegment.length <= 0) return null;
- if (chart.xAxis.noDraw) return null;
- var arguments$ = [chart];
- var axisRepresentation = this.runPrepend("createXAxis", arguments$);
- if (axisRepresentation) return axisRepresentation;
- if (this.mainSeriesRenderer && this.mainSeriesRenderer.createXAxis) {
- axisRepresentation = this.mainSeriesRenderer.createXAxis(chart);
- } else {
- axisRepresentation = this.createTickXAxisWithDates(chart);
- }
- this.headsUpHR();
- this.runAppend("createXAxis", arguments$);
- return axisRepresentation;
- };
-
- /**
- * Creates a label on the x-axis. Generally used to create x-axis labels for drawings.
- *
- * Uses the font properties of the CSS style `stx-float-date` (see *css/stx-chart.css*).
- *
- * **Note:** This function is not used for the floating crosshairs date label, which is also
- * styled using `stx-float-date`. See
- * {@link CIQ.ChartEngine.AdvancedInjectable#updateChartAccessories} and
- * {@link CIQ.ChartEngine.AdvancedInjectable#headsUpHR} for more details.
- *
- * @param {object} params Function parameters.
- * @param {CIQ.ChartEngine.Panel} params.panel The panel on which the label is created.
- * @param {string} params.txt The text for the label.
- * @param {number} params.x The horizontal pixel position on the canvas for the label. **Note:**
- * The function ensures that the label remains on the requested panel if this value is out of
- * bounds.
To get the pixel position for a bar/date use
- * {@link CIQ.ChartEngine#pixelFromTick}, {@link CIQ.ChartEngine#pixelFromDate}, or
- * {@link CIQ.ChartEngine#pixelFromBar}.
- * @param {string} params.backgroundColor The background color for the label.
- * @param {string} [params.color] The foreground color for the label. If none is provided, then
- * white is used, unless the background is white, in which case black is used.
- * @param {boolean} [params.pointed] If true, add an upward pointing triangle to the top edge of
- * the label horizontally centered to form a shape similar to --^--.
- * @param {boolean} [params.padding = 2] The amount of padding in pixels to add to the label text
- * (top, right, bottom, and left).
- *
- * @memberof CIQ.ChartEngine
- * @since 8.1.0 Function signature now includes the `params` object instead of a list of
- * individual parameters. Added the `padding` parameter for easy customization.
- */
- CIQ.ChartEngine.prototype.createXAxisLabel = function (params) {
- if (arguments[0] instanceof CIQ.ChartEngine.Panel) {
- // Handle legacy argument implementation
- params = {
- panel: arguments[0],
- txt: arguments[1],
- x: arguments[2],
- backgroundColor: arguments[3],
- color: arguments[4],
- pointed: arguments[5],
- padding: arguments[6]
- };
- }
- let panel = params.panel;
- let txt = params.txt;
- let x = params.x;
- let backgroundColor = params.backgroundColor;
- let color = params.color;
- let pointed = params.pointed;
- let padding = params.padding === 0 ? 0 : params.padding || 2;
-
- var context = this.chart.context;
- var fontstyle = "stx-float-date"; //or stx_xaxis
- var height = this.getCanvasFontSize(fontstyle) + padding * 2;
- this.canvasFont(fontstyle, context);
- var width;
- try {
- width = context.measureText(txt).width + padding * 2;
- } catch (e) {
- width = 0;
- } // Firefox doesn't like this in hidden iframe
- var y = panel.top + panel.height - height - padding;
- if (x + width / 2 < panel.left || x - width / 2 > panel.right) return; //hopelessly out of bounds
- if (!pointed) {
- if (x + width / 2 > panel.right) x = panel.right - width / 2;
- if (x - width / 2 < panel.left) x = panel.left + width / 2;
- }
- context.fillStyle = backgroundColor;
- CIQ.roundRect({
- ctx: context,
- x: x - width / 2,
- top: y,
- width: width,
- height: height,
- radius: 3,
- fill: true
- });
- var arrowHeight = panel.bottom - panel.yAxis.bottom - height;
- context.beginPath();
- if (pointed) {
- context.moveTo(x - arrowHeight, y);
- context.lineTo(x, y - arrowHeight - 1);
- context.lineTo(x + arrowHeight, y);
- context.closePath();
- context.fill();
- } else {
- context.moveTo(x, y);
- context.lineTo(x, y - arrowHeight);
- context.strokeStyle = backgroundColor;
- context.stroke();
- }
- context.textBaseline = "top";
- context.fillStyle = color
- ? color
- : CIQ.chooseForegroundColor(backgroundColor);
- if (context.fillStyle == backgroundColor) {
- // Best effort to pick a foreground color that isn't the same as the background!
- if (backgroundColor.toUpperCase() == "#FFFFFF")
- context.fillStyle = "#000000";
- else context.fillStyle = "#FFFFFF";
- }
- context.fillText(txt, x - width / 2 + (padding - 1), y + (padding + 2));
- };
-
- };
-
-
- let __js_core_engine_yaxis_ = (_exports) => {
-
-
- var CIQ = _exports.CIQ;
-
- /**
- * Adds text on the canvas for the floating label over the y axis.
- *
- * Uses native canvas functions to add the text. You can override this function if you wish to customize how the text on the floating y axis labels are displayed. See example.
- * @param {object} params
- * @param {object} params.ctx A valid HTML Canvas Context
- * @param {number} params.x Left position of drawing on canvas
- * @param {number} params.txt Text for the label
- * @param {number} params.y Y position of drawing on canvas
- * @param {object} params.margin Margin around the text
- * @param {object} params.margin.left Left margin of text
- * @param {object} params.margin.top Top margin of text
- * @param {number} params.backgroundColor Background color of the shape drawn under the text, if any. This is used to find the text color if there is no color specified
- * @param {number} params.color Text color
- * @memberof CIQ
- * @since 3.0.0
- * @example
- * // customized version which adds a dash before the label text
- * CIQ.createLabel=function(params){
- * // set the vertical alignment of the text
- * params.ctx.textBaseline="middle";
-
- * // set the color for the text and background color behind the text
- * params.ctx.fillStyle=params.color?params.color:CIQ.chooseForegroundColor(params.backgroundColor);
-
- * if( params.ctx.fillStyle === params.backgroundColor){
- * // Best effort to pick a foreground color that isn't the same as the background!
- * if(params.backgroundColor.toUpperCase()=="#FFFFFF")
- * params.ctx.fillStyle="#000000";
- * else
- * params.ctx.fillStyle="#FFFFFF";
- * }
-
- * //add the text to the canvas.
- * // see we are adding a dash ('- ') before the text
- * params.ctx.fillText('- '+params.txt, params.x + params.margin.left, params.y + params.margin.top);
-
- * // set the horizontal alignment of the text
- * params.ctx.textAlign="left";
- * };
- */
- CIQ.createLabel = function (params) {
- params.ctx.textBaseline = "middle";
- params.ctx.fillStyle = params.color
- ? params.color
- : CIQ.chooseForegroundColor(params.backgroundColor);
- if (params.ctx.fillStyle === params.backgroundColor) {
- // Best effort to pick a foreground color that isn't the same as the background!
- if (params.backgroundColor.toUpperCase() == "#FFFFFF")
- params.ctx.fillStyle = "#000000";
- else params.ctx.fillStyle = "#FFFFFF";
- }
- params.ctx.fillText(
- params.txt,
- params.x + params.margin.left,
- params.y + params.margin.top
- );
- params.ctx.textAlign = "left";
- };
-
- /**
- * Displays a floating label over the y axis.
- *
- * Draws a rectangle on the canvas, with an arrowhead on the screen, using using {@link CIQ.roundRect} with an `edge` setting of "arrow".
- * It then calls {@link CIQ.createLabel} to print the text over this background shape; which can be customized to control the text format for these labels.
- *
- * Visual Reference:
- * ![roundRectArrow](roundRectArrow.png "roundRectArrow")
- * @param {object} params
- * @param {object} params.ctx A valid HTML Canvas Context
- * @param {number} params.x Left position of drawing on canvas
- * @param {number} params.top Top position of drawing on canvas
- * @param {number} params.width Width of rectangle
- * @param {number} params.height Height of rectangle
- * @param {number} params.radius Radius of rounding
- * @param {boolean} [params.fill] Whether to fill the background, or just draw the rectangle border.
- * @param {number} [params.txt] Text for the label
- * @param {number} [params.y] Y position of drawing on canvas
- * @param {object} [params.margin] Margin around the text
- * @param {object} [params.margin.left] Left margin of text
- * @param {object} [params.margin.top] Top margin of text
- * @param {number} [params.backgroundColor] Background color. This is the background color of the rectangle.
- * @param {number} [params.color] Text color
- * @memberof CIQ
- * @since 3.0.0 Function signature changed. This function now takes a params object instead of eight different parameters.
- */
- CIQ.roundRectArrow = function (params) {
- CIQ.roundRect(params, "arrow");
- };
-
- /**
- * Displays a floating label over the y axis.
- *
- * Draws a rectangle on the canvas, with just the right side curved corners, using using {@link CIQ.roundRect} with an `edge` setting of "flush".
- * It then calls {@link CIQ.createLabel} to print the text over this background shape; which can be customized to control the text format for these labels.
- *
- * Visual Reference:
- * ![semiRoundRect](semiRoundRect.png "semiRoundRect")
- * @param {object} params
- * @param {object} params.ctx A valid HTML Canvas Context
- * @param {number} params.x Left position of drawing on canvas
- * @param {number} params.top Top position of drawing on canvas
- * @param {number} params.width Width of rectangle
- * @param {number} params.height Height of rectangle
- * @param {number} params.radius Radius of rounding
- * @param {boolean} [params.fill] Whether to fill the background, or just draw the rectangle border.
- * @param {number} [params.txt] Text for the label
- * @param {number} [params.y] Y position of drawing on canvas
- * @param {object} [params.margin] Margin around the text
- * @param {object} [params.margin.left] Left margin of text
- * @param {object} [params.margin.top] Top margin of text
- * @param {number} [params.backgroundColor] Background color. This is the background color of the rectangle.
- * @param {number} [params.color] Text color
- * @memberof CIQ
- * @since 3.0.0 Function signature changed. This function now takes a params object instead of eight different parameters.
- */
- CIQ.semiRoundRect = function (params) {
- CIQ.roundRect(params, "flush");
- };
-
- /**
- * Displays a floating label over the y axis.
- *
- * Draws a rectangle on the canvas using using {@link CIQ.roundRect} with a `radius` of 0
- * It then calls {@link CIQ.createLabel} to print the text over this background shape; which can be customized to control the text format for these labels.
- *
- * Visual Reference:
- * ![rect](rect.png "rect")
- * @param {object} params
- * @param {object} params.ctx A valid HTML Canvas Context
- * @param {number} params.x Left position of drawing on canvas
- * @param {number} params.top Top position of drawing on canvas
- * @param {number} params.width Width of rectangle
- * @param {number} params.height Height of rectangle
- * @param {boolean} [params.fill] Whether to fill the background, or just draw the rectangle border.
- * @param {number} [params.txt] Text for the label
- * @param {number} [params.y] Y position of drawing on canvas
- * @param {object} [params.margin] Margin around the text
- * @param {object} [params.margin.left] Left margin of text
- * @param {object} [params.margin.top] Top margin of text
- * @param {number} [params.backgroundColor] Background color. This is the background color of the rectangle.
- * @param {number} [params.color] Text color
- * @memberof CIQ
- * @since 3.0.0 Function signature changed. This function now takes a params object instead of eight different parameters.
- */
- CIQ.rect = function (params) {
- params.radius = 0;
- CIQ.roundRect(params);
- };
-
- /**
- * Displays floating text label, without any background shapes, over the y axis.
- *
- * Calls {@link CIQ.createLabel}; which can be customized to control the text format for these labels.
- * Will draw text in the color normally used for the background shape. For example, 'green' text for the up tick and 'red' text for a down tick.
- *
- * Visual Reference:
- * ![noop](noop.png "noop")
- * @param {object} params
- * @param {object} params.ctx A valid HTML Canvas Context.
- * @param {number} params.x Left position of drawing on canvas.
- * @param {number} params.txt Text for the label.
- * @param {number} params.y Vertical position of drawing on canvas.
- * @param {object} params.margin Margin around the text.
- * @param {object} params.margin.left Left margin of text.
- * @param {object} params.margin.top Top margin of text.
- * @param {number} params.backgroundColor Text color; since there is no background shape.
- *
- * @memberof CIQ
- * @since
- * - 3.0.0 Function signature changed. This function now takes a params object instead of eight different parameters.
- * - 5.2.1 Will now draw text in the color normally used for the background shape. For example, 'green' text for the up tick and 'red' text for a down tick.
- */
- CIQ.noop = function (params) {
- params.color = params.backgroundColor;
- CIQ.createLabel(params);
- };
-
- /**
- * Displays a floating label over the y axis.
- *
- * Draws a 'ticked' rectangle on the canvas, using using {@link CIQ.roundRect}.
- * It then calls {@link CIQ.createLabel} to print the text over this background shape; which can be customized to control the text format for these labels.
- *
- * Visual Reference:
- * ![tickedRect](tickedRect.png "tickedRect")
- * @param {object} params
- * @param {object} params.ctx A valid HTML Canvas Context
- * @param {number} params.x Left position of drawing on canvas
- * @param {number} params.top Top position of drawing on canvas
- * @param {number} params.width Width of rectangle
- * @param {number} params.height Height of rectangle
- * @param {number} params.radius Radius of rounding
- * @param {boolean} [params.fill] Whether to fill the background, or just draw the rectangle border.
- * @param {number} [params.txt] Text for the label
- * @param {number} [params.y] Y position of drawing on canvas
- * @param {object} [params.margin] Margin around the text
- * @param {object} [params.margin.left] Left margin of text
- * @param {object} [params.margin.top] Top margin of text
- * @param {number} [params.backgroundColor] background color. This is the background color of the rectangle.
- * @param {number} [params.color] Text color
- * @memberof CIQ
- * @since 3.0.0 Function signature changed. This function now takes a params object instead of eight different parameters.
- */
-
- CIQ.tickedRect = function (params) {
- CIQ.rect(params);
- var tickY = Math.round(params.top + params.height / 2) + 0.5;
- params.ctx.beginPath();
- params.ctx.moveTo(params.x - 2, tickY);
- params.ctx.lineTo(params.x, tickY);
- params.ctx.stroke();
- params.ctx.closePath();
- };
-
- /**
- * Displays a floating label over the y axis.
- *
- * Draws a rectangle, with curved corners, on the canvas.
- * It then calls {@link CIQ.createLabel} to print the text over this background shape; which can be customized to control the text format for these labels.
- *
- * Visual Reference:
- * ![roundRect](roundRect.png "roundRect")
- * @param {object} params
- * @param {object} params.ctx A valid HTML Canvas Context
- * @param {number} params.x Left position of drawing on canvas
- * @param {number} params.top Top position of drawing on canvas
- * @param {number} params.width Width of rectangle
- * @param {number} params.height Height of rectangle
- * @param {number} params.radius Radius of rounding
- * @param {boolean} [params.fill] Whether to fill the background, or just draw the rectangle border.
- * @param {number} [params.txt] Text for the label
- * @param {number} [params.y] Y position of drawing on canvas
- * @param {object} [params.margin] Margin around the text
- * @param {object} [params.margin.left] Left margin of text
- * @param {object} [params.margin.top] Top margin of text
- * @param {number} [params.backgroundColor] background color. This is the background color of the rectangle.
- * @param {number} [params.color] Text color
- * @param {string} [edge] "flush","arrow"
- * @memberof CIQ
- * @since 3.0.0 Function signature changed. This function now takes a params object and the drawing type instead of eight different parameters.
- * Also, this function will draw the label if `params.txt` is present, otherwise just the floating label outline will be drawn.
- */
- CIQ.roundRect = function (params, edge) {
- if (arguments.length === 9) {
- params = {
- ctx: arguments[0],
- x: arguments[1],
- top: arguments[2],
- width: arguments[3],
- height: arguments[4],
- radius: arguments[5],
- fill: arguments[6],
- stroke: arguments[7],
- edge: arguments[8]
- };
- }
- var stroke = params.stroke;
- var x = params.x;
- var y = params.top;
- var width = params.width;
- var height = params.height;
- var radius = params.radius;
- var fill = params.fill;
- var ctx = params.ctx;
- if (typeof stroke == "undefined") {
- stroke = true;
- }
- if (typeof radius === "undefined") {
- radius = 5;
- if (width < 0) radius = -5;
- }
- var yradius = width < 0 ? radius * -1 : radius;
- if (radius && !edge) x = x - 1; // Just a smidge more
-
- var xr = x + radius,
- xw = x + width,
- yr = y + yradius,
- yh = y + height;
- var xwr = xw - radius,
- yhr = yh - yradius;
- ctx.beginPath();
- ctx.moveTo(xr, y);
- ctx.lineTo(xwr, y);
-
- ctx.quadraticCurveTo(xw, y, xw, yr);
- ctx.lineTo(xw, yhr);
- ctx.quadraticCurveTo(xw, yh, xwr, yh);
- ctx.lineTo(xr, yh);
-
- if (edge == "flush") {
- ctx.lineTo(x, yh);
- ctx.lineTo(x, y);
- } else if (edge == "arrow") {
- ctx.quadraticCurveTo(x, yh, x - radius, yhr);
- var multiplier = width < 0 ? 1 : -1;
- ctx.lineTo(x + (height / 2) * multiplier, y + height / 2); // right arrow tip
- ctx.lineTo(x - radius, yr);
- ctx.quadraticCurveTo(x, y, xr, y);
- } else {
- ctx.quadraticCurveTo(x, yh, x, yhr);
- ctx.lineTo(x, yr);
- ctx.quadraticCurveTo(x, y, xr, y);
- }
- ctx.closePath();
- if (params.backgroundColor) ctx.fillStyle = params.backgroundColor;
-
- if (stroke) {
- ctx.stroke();
- }
- if (fill) {
- ctx.fill();
- }
- if (params.txt) CIQ.createLabel(params);
- };
-
- /**
- * Defines an object used for rendering the Y-axis on a panel.
- *
- * Each panel object will **automatically** include a YAxis object, which can be adjusted immediately after declaring your `new CIQ.ChartEngine();`
- * Any adjustments to the y-axis members after it has been rendered will require a [draw()]{@link CIQ.ChartEngine#draw} call to apply the changes. A call to [initializeChart()]{@link CIQ.ChartEngine#initializeChart} may be required as well, depending on the setting being changed. See examples.
- *
- * Also see:
- * - {@link CIQ.ChartEngine#yaxisLabelStyle}
- * - {@link CIQ.ChartEngine#yTolerance}
- * - {@link CIQ.ChartEngine.Chart#yaxisPaddingRight}
- * - {@link CIQ.ChartEngine.Chart#yaxisPaddingLeft}
- *
- * For full customization instructions see:
- * - {@tutorial Gridlines and axis labels}
- * - {@link CIQ.ChartEngine.AdvancedInjectable#createYAxis}
- * - {@link CIQ.ChartEngine.AdvancedInjectable#drawYAxis}
- *
- * Example: stxx.panels['chart'].yAxis
- *
- * Example: stxx.chart.yAxis (convenience shortcut for accessing the main panel object - same as above)
- *
- * Example: stxx.panels['Aroon (14)'].yAxis
- *
- * **Note:** If modifying a y-axis placement setting (widht, margins, position left/right, etc) after the axis has been rendered, you will need to call
- * {@link CIQ.ChartEngine#calculateYAxisMargins} or {@link CIQ.ChartEngine#calculateYAxisPositions} followed by {@link CIQ.ChartEngine#draw} to activate the change.
- *
- * @constructor
- * @name CIQ.ChartEngine.YAxis
- * @param {object} init Object containing custom values for Y-axis members
- * @example
- * // here is an example on how to override the default top and bottom margins after the initial axis has already been rendered
- * stxx.loadChart(symbol, {masterData: yourData}, function () {
- * // callback - your code to be executed after the chart is loaded
- * stxx.chart.yAxis.initialMarginTop=50;
- * stxx.chart.yAxis.initialMarginBottom=50;
- * stxx.calculateYAxisMargins(stxx.chart.panel.yAxis); // must recalculate the margins after they are changed.
- * stxx.draw();
- * });
- * @example
- * // here is an example on how to override the default top and bottom margins before the initial axis has been rendered
- * var stxx=new CIQ.ChartEngine({container:document.querySelector(".chartContainer"), layout:{"candleWidth": 16, "crosshair":true}});
- * stxx.setPeriodicity({period:1, interval:1, timeUnit:"minute"}); // set your default periodicity to match your data. In this case one minute.
- * stxx.chart.yAxis.initialMarginTop=50; // set default margins so they do not bump on to the legend
- * stxx.chart.yAxis.initialMarginBottom=50;
- * stxx.loadChart("SPY", {masterData: yourData});
- * @example
- * // here is an example on how to turn off the last price label (main chart panel) before the initial axis has already been rendered
- * var stxx=new CIQ.ChartEngine({container:document.querySelector(".chartContainer"), layout:{"candleWidth": 16, "crosshair":true}});
- * stxx.chart.panel.yAxis.drawCurrentPriceLabel=false;
- *
- * @since 5.1.0 Created a name member which is used to determine if the y-axis is the same as another.
- */
- CIQ.ChartEngine.YAxis = function (init) {
- for (var field in init) this[field] = init[field];
- if (!this.name) this.name = CIQ.uniqueID();
- if (this.position == "none") this.width = 0;
- };
-
- CIQ.extend(
- CIQ.ChartEngine.YAxis.prototype,
- {
- high: null, // High value on y axis (read only)
- low: null, // Low value on y axis (read only)
- shadow: null, // high - low (read only)
- logHigh: null, // High log value on y axis (read only)
- logLow: null, // Low log value on y axis (read only)
- logShadow: null, // logHigh - logLow (read only)
- multiplier: null, // Computed automatically. Divide pixel by this to get the price (then add to low). Or multiply price by this to get the pixel (then add to top)
- bottom: null, // calculated automatically (panel.bottom-yAxis.bottomOffset)
- top: null, // calculated automatically (panel.top+yAxis.topOffset;)
- height: null, // bottom - top
- left: null, // calculated left position on canvas to begin drawing.
- width: null, // calculated width of y axis
- renderers: [], // calculated automatically, array of renderers plotting on this axis
- studies: [] // calculated automatically, array of studies plotting on this axis
- },
- true
- );
-
- /**
- * Default setting for the array that determines how many decimal places to print based on the size of the shadow (the difference between chart high and chart low).
- * The array consists of tuples in descending order. If the shadow is less than n1 then n2 decimal places will be printed.
- * See {@link CIQ.ChartEngine.YAxis#shadowBreaks}
- * @type array
- * @memberof CIQ.ChartEngine.YAxis
- * @since
- * - 2015-11-1
- * - 5.2.0 Additional break added.
- * @default
- */
- CIQ.ChartEngine.YAxis.defaultShadowBreaks = [
- [1000, 2],
- [5, 4],
- [0.001, 8]
- ];
-
- /**
- * Alternative setting (for small charts) array that determines how many decimal places to print based on the size of the shadow (the difference between chart high and chart low).
- * The array consists of tuples in descending order. If the shadow is less than n1 then n2 decimal places will be printed.
- * See {@link CIQ.ChartEngine.YAxis#shadowBreaks}
- * @type array
- * @memberof CIQ.ChartEngine.YAxis
- * @since 2015-11-1
- * @default
- */
- CIQ.ChartEngine.YAxis.smallChartShadowBreaks = [
- [10, 2],
- [1, 4]
- ];
-
- /**
- * Controls maximum number of decimal places to ever display on a y-axis floating price label.
- *
- * Set to the maximum decimal places from 0 to 10, or leave null and the chart will choose automatically based on {@link CIQ.ChartEngine.YAxis#shadowBreaks}.
- * - See {@link CIQ.ChartEngine.YAxis#decimalPlaces} for controlling decimal places on the axis itself.
- * - See {@link CIQ.ChartEngine.YAxis#width} and {@link CIQ.ChartEngine.Chart#dynamicYAxis} to manage the width of the y axis.
- * @type number
- * @default
- * @memberof CIQ.ChartEngine.YAxis
- * @since 5.2.1 Default changed to null.
- */
- CIQ.ChartEngine.YAxis.prototype.maxDecimalPlaces = null;
-
- /**
- * Optionally hard set the high (top value) of the yAxis (for instance when plotting 0 - 100% charts)
- * @type number
- * @default
- * @memberof CIQ.ChartEngine.YAxis
- */
- CIQ.ChartEngine.YAxis.prototype.max = null;
-
- /**
- * Optionally hard set the low (bottom value) of the yAxis (for instance when plotting 0 - 100% charts)
- * @type number
- * @default
- * @memberof CIQ.ChartEngine.YAxis
- */
- CIQ.ChartEngine.YAxis.prototype.min = null;
-
- /**
- * Controls the number of decimal places on the y axis labels.
- *
- * Set to the preferred number of decimal places from 0 to 10, or leave null and the chart will choose automatically based on {@link CIQ.ChartEngine.YAxis#shadowBreaks}
- *
- * Each y axis will make its own determination, so to override this value for all axes, you must adjust the y axis prototype.
- *
Example: `CIQ.ChartEngine.YAxis.prototype.decimalPlaces=4;`
- *
- * **Note:** study panel axis may be condensed using {@link CIQ.condenseInt}. See {@link CIQ.ChartEngine#formatYAxisPrice} for all details.
- *
- * - See {@link CIQ.ChartEngine.YAxis#maxDecimalPlaces} for further controlling decimal places on floating labels.
- * - See {@link CIQ.ChartEngine.YAxis#width} and {@link CIQ.ChartEngine.Chart#dynamicYAxis} to manage the width of the y axis.
- * - See {@link CIQ.ChartEngine.YAxis#shadowBreaks} to override how many decimal places to print based on the size of the shadow (the difference between chart high and chart low).
- *
- * @type number
- * @default
- * @memberof CIQ.ChartEngine.YAxis
- * @since 5.2.0 Default changed to null.
- */
- CIQ.ChartEngine.YAxis.prototype.decimalPlaces = null;
-
- /**
- * Ideal size between y-axis values in pixels. Leave null to automatically calculate.
- * See {@tutorial Gridlines and axis labels} for additional details.
- * @type number
- * @default
- * @memberof CIQ.ChartEngine.YAxis
- */
- CIQ.ChartEngine.YAxis.prototype.idealTickSizePixels = null;
-
- /**
- * Set to specify that the y-axis vertical grid be drawn with specific intervals between ticks.
- * This amount will be overridden if it will result in y axis crowding.
- * In which chase, multiples of the original interval will be used.
- * For example, if `.25` is selected, and that will cause labels to be on top of or too close to each other, `.50` may be used.
- * Crowding is prevented by allowing for a minimum of space equating the y-axis font height between labels.
- *
- * **This parameter is also used in the 'Trade From Chart' (TFC) module**. If set, it will force the widget to skip certain price values and instead 'snap' to your desired intervals. This will guarantee that an order is only placed at the allowed price intervals for the security in question.
- *
- * **Note that this parameter will be ignored if {@link CIQ.ChartEngine.YAxis#pretty} is set to `true`. If you require specific price intervals, please set {@link CIQ.ChartEngine.YAxis#pretty} to 'false' before setting `minimumPriceTick`**
- *
- * Visual Reference:
- * ![yAxis.minimumPriceTick](yAxis.minimumPriceTick.png "yAxis.minimumPriceTick")
- *
- * @type number
- * @default
- * @memberof CIQ.ChartEngine.YAxis
- * @example
- * // Declare a CIQ.ChartEngine object. This is the main object for drawing charts
- * var stxx=new CIQ.ChartEngine({container:document.querySelector(".chartContainer"), layout:{"candleWidth": 16, "crosshair":true}});
- * // set interval between ticks
- * stxx.chart.yAxis.minimumPriceTick=.50;
- */
- CIQ.ChartEngine.YAxis.prototype.minimumPriceTick = null;
-
- /**
- * Set to specify that the y-axis vertical grid be drawn with fractional intervals.
- *
- * This is checked in {@link CIQ.ChartEngine.AdvancedInjectable#drawYAxis} and if it is not null,
- * and there is no existing [yAxis.priceFormatter]{@link CIQ.ChartEngine.YAxis#priceFormatter}, one is created to specially format the y-axis ticks.
- *
- * {@link CIQ.ChartEngine.YAxis#decimalPlaces} and {@link CIQ.ChartEngine.YAxis#maxDecimalPlaces} will not be honored in this mode.
- *
- * To disable the formatting you must reset both the yAxis.priceFormatter and this fractional object to 'null'.
- *
Example:
- * ```
- * stxx.chart.yAxis.priceFormatter=stxx.chart.yAxis.fractional=null;
- * ```
- *
- * If the outlined logic is not suitable for your needs, you will need to create your own [yAxis.priceFormatter]{@link CIQ.ChartEngine.YAxis#priceFormatter}
- *
- * @type object
- * @default
- * @memberof CIQ.ChartEngine.YAxis
- * @example
- * ![yAxis.drawCurrentPriceLabel](drawCurrentPriceLabel.png "yAxis.drawCurrentPriceLabel")
- * @type boolean
- * @default
- * @memberof CIQ.ChartEngine.YAxis
- * @since 04-2015
- */
- CIQ.ChartEngine.YAxis.prototype.drawCurrentPriceLabel = true;
-
- /**
- * Set to `false` to hide the series price labels in the main panel's y-axis.
- *
- * @type boolean
- * @default
- * @memberof CIQ.ChartEngine.YAxis
- * @since 3.0.0
- */
- CIQ.ChartEngine.YAxis.prototype.drawSeriesPriceLabels = true;
-
- /**
- * Set to false to hide **all** price labels on the particular y axis.
- *
See {@link CIQ.ChartEngine.YAxis#drawCurrentPriceLabel} to disable just the current price label on the main chart panel.
- *
See CIQ.ChartEngine.preferences.labels to disable just the last value label on studies.
- * @type boolean
- * @default
- * @memberof CIQ.ChartEngine.YAxis
- * @since 04-2015
- */
- CIQ.ChartEngine.YAxis.prototype.drawPriceLabels = true;
-
- /**
- * When `true`, will attempt to create grid lines that approximate a `golden ratio` between x and y axis by basing grid on {@link CIQ.ChartEngine.YAxis#idealTickSizePixels}.
- * This creates an "airy" modern looking chart.
- * If set to false, each axis will be adjusted separately and may create long and narrow rectangular grids depending on date or price range.
- * @type boolean
- * @default
- * @memberof CIQ.ChartEngine.YAxis
- * @since
- * - 04-2015
- * - 4.0.0 Now defaults to true.
- */
- CIQ.ChartEngine.YAxis.prototype.goldenRatioYAxis = true;
-
- /**
- * Shape of the floating y axis label.
- *
- * Available options:
- * - ["roundRectArrow"]{@link CIQ.roundRectArrow}
- * - ["semiRoundRect"]{@link CIQ.semiRoundRect}
- * - ["roundRect"]{@link CIQ.roundRect}
- * - ["tickedRect"]{@link CIQ.tickedRect}
- * - ["rect"]{@link CIQ.rect}
- * - ["noop"]{@link CIQ.noop}
- *
- * It will default to {@link CIQ.ChartEngine#yaxisLabelStyle}.
- * This could be set independently on each panel if desired.
- * @type string
- * @default
- * @memberof CIQ.ChartEngine.YAxis
- * @since 04-2015
- * @example
- * var stxx=new CIQ.ChartEngine({container:document.querySelector(".chartContainer"), layout:{"candleWidth": 16, "crosshair":true}});
- * stxx.chart.yAxis.yaxisLabelStyle="rect"
- */
- CIQ.ChartEngine.YAxis.prototype.yaxisLabelStyle = null;
-
- /**
- * Set to `true` to right justify the yaxis labels
- * Set to `false` to force-left justify the labels, even when the axis is on the left.
- * Set to null to have the justification automatically adjusted based on the axis position. Right axis will justify left, and left axis will justify right.
- *
- *
- * This setting does not control the floating last price. See {@link CIQ.ChartEngine.AdvancedInjectable#drawCurrentHR} and {@link CIQ.ChartEngine#createYAxisLabel}
- * @type boolean
- * @default
- * @memberof CIQ.ChartEngine.YAxis
- * @since
- * - 15-07-01
- * - 6.2.0 Formalized distinction between null and false values.
- */
- CIQ.ChartEngine.YAxis.prototype.justifyRight = null;
-
- /**
- * Setting to true causes the y-axis and all linked drawings, series and studies to display inverted (flipped) from its previous state.
- * @type boolean
- * @default
- * @memberof CIQ.ChartEngine.YAxis
- * @since 6.3.0
- */
- CIQ.ChartEngine.YAxis.prototype.flipped = false;
-
- /**
- * Set to true to put a rectangle behind the yaxis text (use with {@link CIQ.ChartEngine.Chart#yaxisPaddingRight} and {@link CIQ.ChartEngine.Chart#yaxisPaddingLeft})
- * @type boolean
- * @default
- * @memberof CIQ.ChartEngine.YAxis
- * @since 15-07-01
- */
- CIQ.ChartEngine.YAxis.prototype.textBackground = false;
-
- /**
- * Optional function used to override default formatting of Y-axis values, including the floating HUD value of the crosshair.
- *
- * Expected format :
- *
- * function(stx, panel, price, decimalPlaces)
- *
- * Parameters:
- *
- * stx - {@link CIQ.ChartEngine} - The chart object
- * panel - {@link CIQ.ChartEngine.Panel} - The panel
- * price - number - The price to format
- * decimalPlaces - number - Optional - Number of decimal places to use
- * (may not always be present)
- *
- * Returns:
- *
- * text - Formatted text label for the price
- *
- * @type function
- * @example
- * stxx.chart.yAxis.priceFormatter=function(stx, panel, price, decimalPlaces){
- * var convertedPrice;
- * // add our logic here to convert 'price' to 'convertedPrice'
- * return convertedPrice; // string
- * }
- * @default
- * @memberof CIQ.ChartEngine.YAxis
- */
- CIQ.ChartEngine.YAxis.prototype.priceFormatter = null;
-
- /**
- * Sets the y-axis bottom on any panel.
- * Rendering will start this number of pixels above the panel's bottom.
- * Note that {@link CIQ.ChartEngine#adjustPanelPositions} and {@link CIQ.ChartEngine#draw} will need to be called to immediately activate this setting after the axis has already been drawn.
- *
- * Visual Reference:
- * ![yAxis.width](yAxis.bottomOffset.png "yAxis.bottomOffset")
- * ![yAxis.width](yAxis.bottomTopOffset.png "yAxis.bottomTopOffset")
- * @type number
- * @default
- * @memberof CIQ.ChartEngine.YAxis
- * @example
- * // The list of current panels can be found in "stxx.panels".
- * stxx.panels[panelName].yAxis.bottomOffset=20;
- * stxx.panels[panelName].yAxis.topOffset=60;
- * stxx.adjustPanelPositions(); // !!!! must recalculate the margins after they are changed. !!!!
- * stxx.draw();
- */
- CIQ.ChartEngine.YAxis.prototype.bottomOffset = 0;
-
- /**
- * Sets y-axis top on Study panels.
- * Rendering will start this number of pixels below the panel's top.
- * Note that {@link CIQ.ChartEngine#adjustPanelPositions} and {@link CIQ.ChartEngine#draw} will need to be called to immediately activate this setting after the axis has already been drawn.
- *
- * Visual Reference:
- * ![yAxis.width](yAxis.bottomTopOffset.png "yAxis.bottomTopOffset")
- * @type number
- * @default
- * @memberof CIQ.ChartEngine.YAxis
- * @example
- * // The list of current panels can be found in "stxx.panels".
- * stxx.panels[panelName].yAxis.bottomOffset=20;
- * stxx.panels[panelName].yAxis.topOffset=60;
- * stxx.adjustPanelPositions(); // !!!! must recalculate the margins after they are changed. !!!!
- * stxx.draw();
- */
- CIQ.ChartEngine.YAxis.prototype.topOffset = 0;
-
- /**
- * Set this to automatically compress and offset the y-axis so that this many pixels of white space are above the display.
- * Note that {@link CIQ.ChartEngine#calculateYAxisMargins} and {@link CIQ.ChartEngine#draw} will need to be called to immediately activate this setting after the axis has already been drawn.
- *
- * Visual Reference:
- * ![yAxis.width](yAxis.initialMarginTop.png "yAxis.initialMarginTop")
- * @type number
- * @default
- * @memberof CIQ.ChartEngine.YAxis
- *
- * @example
- * // Here is an example on how to override the default top and bottom margins before the initial axis has been rendered.
- * var stxx=new CIQ.ChartEngine({container:document.querySelector(".chartContainer"), layout:{"candleWidth": 16, "crosshair":true}});
- * stxx.setPeriodicity({period:1, interval:1, timeUnit:"minute"}); // Set your default periodicity to match your data; in this case, one minute.
- * stxx.chart.yAxis.initialMarginTop = 50; // Set default margins so they do not bump on to the legend.
- * stxx.chart.yAxis.initialMarginBottom = 50;
- * stxx.loadChart("SPY", {masterData: yourData});
- *
- * @example
- * // Here is an example on how to override the default top and bottom margins after the initial axis has already been rendered.
- * stxx.loadChart(symbol, {masterData: yourData}, function () {
- * var yAxis = stxx.chart.yAxis;
- *
- * yAxis.initialMarginTop = 50;
- * yAxis.initialMarginBottom = 50;
- *
- * // !! Must recalculate margins after they are changed!
- * stxx.calculateYAxisMargins(yAxis);
- * stxx.draw();
- * });
- *
- * @example
- * // Here is an example on how to override the default top and bottom margins for a specific panel after the initial axis has already been rendered.
- * // The list of current panels can be found in stxx.panels.
- * stxx.panels[panelName].yAxis.initialMarginTop = 100;
- * stxx.panels[panelName].yAxis.initialMarginBottom = 100;
- * stxx.calculateYAxisMargins(stxx.panels[panelName].yAxis); // !!!! Must recalculate the margins after they are changed. !!!!
- * stxx.draw();
- */
- CIQ.ChartEngine.YAxis.prototype.initialMarginTop = 10;
-
- /**
- * Set this to automatically compress and offset the y-axis so that this many pixels of white space are below the display.
- * Note that {@link CIQ.ChartEngine#calculateYAxisMargins} and {@link CIQ.ChartEngine#draw} will need to be called to immediately activate this setting after the axis has already been drawn.
- *
- * Visual Reference:
- * ![yAxis.width](yAxis.initialMarginTop.png "yAxis.initialMarginTop")
- * @type number
- * @default
- * @memberof CIQ.ChartEngine.YAxis
- *
- * @example
- * // Here is an example on how to override the default top and bottom margins before the initial axis has been rendered.
- * var stxx=new CIQ.ChartEngine({container:document.querySelector(".chartContainer"), layout:{"candleWidth": 16, "crosshair":true}});
- * stxx.setPeriodicity({period:1, interval:1, timeUnit:"minute"}); // Set your default periodicity to match your data; in this case, one minute.
- * stxx.chart.yAxis.initialMarginTop = 50; // Set default margins so they do not bump on to the legend.
- * stxx.chart.yAxis.initialMarginBottom = 50;
- * stxx.loadChart("SPY", {masterData: yourData});
- * @example
- * // Here is an example on how to override the default top and bottom margins after the initial axis has already been rendered.
- * stxx.loadChart(symbol, {masterData: yourData}, function() {
- * // Callback -- your code to be executed after the chart is loaded.
- * stxx.chart.yAxis.initialMarginTop = 50;
- * stxx.chart.yAxis.initialMarginBottom = 50;
- * stxx.calculateYAxisMargins(stxx.chart.panel.yAxis); // !!!! Must recalculate the margins after they are changed. !!!!
- * stxx.draw();
- * });
- */
- CIQ.ChartEngine.YAxis.prototype.initialMarginBottom = 10;
-
- /**
- * Sets the vertical zoom level for the y axis and all its associated series.
- *
- * It can be set programmatically or by the user as they grab the y axis and move it up or down.
- *
- * The value represents the number of pixels to zoomed in or out, and can be positive or negative.
- * The larger the number, the more it zooms out to show a wider price range.
- *
- * Please note that the zoom level will be reset as determined by {@link CIQ.ChartEngine.YAxis#initialMarginTop} and
- * {@link CIQ.ChartEngine.YAxis#initialMarginBottom} when a {@link CIQ.ChartEngine#loadChart} is rendered, the {@link CIQ.ChartEngine#home} button is pressed, or when {@link CIQ.ChartEngine.AdvancedInjectable#touchDoubleClick} is activated on a touch device.
- *
- * @type number
- * @default
- * @example
- * // programmatically change the vertical zoom level for the primary chart yAxis
- * stxx.chart.yAxis.zoom=100;stxx.draw();
- * @memberof CIQ.ChartEngine.YAxis
- */
- CIQ.ChartEngine.YAxis.prototype.zoom = 0;
-
- /**
- * set this to the number of pixels to offset the y-axis, positive or negative.
- * @type number
- * @default
- * @memberof CIQ.ChartEngine.YAxis
- */
- CIQ.ChartEngine.YAxis.prototype.scroll = 0;
-
- /**
- * Set this to a factor to scale the y axis.
- *
- * The zoom value will be internality adjusted based on the value of this property as follows:
- * ```
- * zoom = (1-heightFactor)*height + initial margin settings
- * ```
- * For example, use this to easily reduce the scale of the axis by 20%, set heightFactor=0.8.
- *
- * @type number
- * @default
- * @memberof CIQ.ChartEngine.YAxis
- * @since 7.0.0
- */
- CIQ.ChartEngine.YAxis.prototype.heightFactor = 1;
-
- // get/set width to allow {@link CIQ.ChartEngine.Chart#dynamicYAxis} feature
- // to set _dynamicWidth instead of _width. This allows user widths to be
- // restored easily when the feature is not needed.
- Object.defineProperty(CIQ.ChartEngine.YAxis.prototype, "width", {
- configurable: true,
- enumerable: true,
- get: function () {
- // _dynamicWidth is set by {@link CIQ.ChartEngine#drawYAxis} and
- // cleared by {@link CIQ.ChartEngine.Chart#resetDynamicYAxis}
- return this._dynamicWidth || this._width;
- },
- set: function (value) {
- this._width = value;
- // the calculated width is less than user value, getter should return the user value
- if (this._dynamicWidth < value) this._dynamicWidth = NaN;
- }
- });
-
- /**
- * The y-axis width in pixels.
- *
- * ![yAxis.width](yAxis.width.png "yAxis.width")
- *
- * @type number
- * @default
- * @memberof CIQ.ChartEngine.YAxis
- *
- * @see {@link CIQ.ChartEngine.Chart#dynamicYAxis} to set the y-axis width dynamically.
- * @see {@link CIQ.ChartEngine.Chart#yaxisPaddingRight} and
- * {@link CIQ.ChartEngine.Chart#yaxisPaddingLeft} for information on how to overlay the y-axis onto
- * the chart.
- *
- * @example
- * For the primary panel prices will be condensed if the price differential between two ticks is equal or over 20000.
- * This can be overridden by manually setting {@link CIQ.ChartEngine.YAxis#decimalPlaces}.
- *
- * You can call this method to ensure that any prices that you are using outside of the chart are formatted the same as the prices on the y-axis.
- * @param {number} price The price to be formatted
- * @param {CIQ.ChartEngine.Panel} panel The panel for the y-axis.
- * @param {number} [requestedDecimalPlaces] Number of decimal places, otherwise it will be determined by the yaxis setting, or if not set, determined automatically
- * @param {CIQ.ChartEngine.YAxis} [yAxis] yAxis. If not present, the panel's y-axis will be used.
- * @param {boolean} [internationalize] Normally this function will return an internationalized result. Set this param to false to bypass.
- * @return {number} The formatted price
- * @memberof CIQ.ChartEngine
- * @since
- * - 4.0.0 CondenseInt will be called only if yaxis priceTick equal or over 1000 for studies and 20000 for primary axis, rather than 100.
- * - 5.2.0 All axes will be condensed to some degree to allow for more uniform decimal precision.
- * - 6.1.0 Added `internationalize` parameter.
- */
- CIQ.ChartEngine.prototype.formatYAxisPrice = function (
- price,
- panel,
- requestedDecimalPlaces,
- yAxis,
- internationalize
- ) {
- if (price === null || typeof price == "undefined" || isNaN(price)) return "";
- if (!panel) panel = this.chart.panel;
- var yax = yAxis ? yAxis : panel.yAxis;
- var decimalPlaces = requestedDecimalPlaces;
- if (!decimalPlaces && decimalPlaces !== 0)
- decimalPlaces = yax.printDecimalPlaces;
- if (!decimalPlaces && decimalPlaces !== 0) {
- decimalPlaces = this.decimalPlacesFromPriceTick(yax.priceTick);
- }
- var minCondense = yax == panel.chart.yAxis ? 20000 : 1000;
- if (yax.priceTick >= minCondense) {
- price = price.toFixed(decimalPlaces); // k or m for thousands or millions
- return CIQ.condenseInt(price);
- }
-
- var internationalizer = this.internationalizer;
- if (internationalizer && internationalize !== false) {
- var l = internationalizer.priceFormatters.length;
- if (decimalPlaces >= l) decimalPlaces = l - 1;
- price = internationalizer.priceFormatters[decimalPlaces].format(price);
- } else {
- price = price.toFixed(decimalPlaces);
- // the above may be a problem at some point for datasets with very small shadows because the rounding skews the real number.
- // We should truncate the decimal places instead of rounding to preserve the accuracy,
- // but for now the above seems to work fine so we will leave it alone.
- // And also the amount of rounding being done here actually "corrects" some of differences introduced elsewhere in the yAxis price calculations. ugg!
- // Use the flowing code when ready to show truncated vs. rounded values
- //price = price.toString();
- //if(price.indexOf(".") > 0){
- // price = price.slice(0, (price.indexOf("."))+decimalPlaces+1)
- //};
- }
- return price;
- };
-
- /**
- * Calculates the range for the y-axis and sets appropriate member variables.
- *
- * The default behavior is to stop vertical scrolling when only 1/5 of the chart remains on
- * screen, so the primary chart never completely scrolls off the screen — unless you start
- * zooming the y-axis by grabbing it and pulling it down. Once the zoom level goes into the
- * negative range (meaning that you are shrinking the chart vertically) the vertical panning
- * limitation goes away.
- *
- * This method should seldom if ever be called directly. But you can override this behavior (so
- * that a chart is always allowed to completely scroll off the screen at any zoom level) with
- * the following code:
- * ```
- * stxx.originalcalculateYAxisRange = stxx.calculateYAxisRange;
- * CIQ.ChartEngine.prototype.calculateYAxisRange = function(panel, yAxis, low, high) {
- * var beforeScroll = this.chart.yAxis.scroll;
- * this.originalcalculateYAxisRange(panel, yAxis, low, high);
- * this.chart.yAxis.scroll = beforeScroll;
- * };
- * ```
- *
- * @param {CIQ.ChartEngine.Panel} panel The panel containing the y-axis.
- * @param {CIQ.ChartEngine.YAxis} yAxis The y-axis for which the range is calculated.
- * @param {number} [low] The low value for the axis.
- * @param {number} [high] The high value for the axis.
- *
- * @memberof CIQ.ChartEngine
- * @since 5.2.0 When the y-axis is zoomed in, there is no limitation on vertical panning.
- */
- CIQ.ChartEngine.prototype.calculateYAxisRange = function (
- panel,
- yAxis,
- low,
- high
- ) {
- if (low == Number.MAX_VALUE) {
- low = 0;
- high = 0;
- }
- var cheight = panel.height,
- newHigh = null,
- newLow = null;
- this.adjustYAxisHeightOffset(panel, yAxis);
- yAxis.height = yAxis.bottom - yAxis.top;
- // Ensure the user hasn't scrolled off the top or the bottom of the chart when the chart is not zoomed in
- var verticalPad = Math.round(Math.abs(cheight / 5));
- if (yAxis.zoom >= 0 && cheight - Math.abs(yAxis.scroll) < verticalPad) {
- yAxis.scroll = (cheight - verticalPad) * (yAxis.scroll < 0 ? -1 : 1);
- }
-
- var isChartMainAxis =
- panel.chart.name === panel.name && panel.yAxis.name === yAxis.name;
- var isLogScale =
- low > 0 &&
- (this.layout.semiLog || this.layout.chartScale == "log") &&
- !panel.chart.isComparison &&
- this.layout.aggregationType != "pandf";
-
- if (low || low === 0) {
- if (high - low === 0) {
- // A stock that has no movement, so we create some padding so that a straight line will appear
- var padding = Math.pow(10, -(low.toString() + ".").split(".")[1].length);
- if (padding == 1) padding = 100; // For whole number prices, widen the shadow
- newHigh = low + padding;
- newLow = low - padding;
- } else {
- if (isChartMainAxis && isLogScale && (high || high === 0)) {
- // When in log scale, the yAxis high and low will be the log10 of the prices. The actual values are just for display, not for calculation.
- var logLow = Math.log(low) / Math.LN10;
- var logHigh = Math.log(high) / Math.LN10;
- newHigh = Math.pow(10, logHigh);
- newLow = Math.pow(10, logLow);
- } else {
- newHigh = high;
- newLow = low;
- }
- }
- yAxis.high = newHigh;
- yAxis.low = newLow;
- }
- if (yAxis.max || yAxis.max === 0) yAxis.high = yAxis.max;
- if (yAxis.min || yAxis.min === 0) yAxis.low = yAxis.min;
- yAxis.shadow = yAxis.high - yAxis.low;
- if (isChartMainAxis) {
- // For the main yaxis on the main chart only check for semilog and flipped
- if (yAxis.semiLog != isLogScale) {
- this.clearPixelCache();
- yAxis.semiLog = isLogScale;
- }
- yAxis.flipped = this.layout.flipped;
- }
- };
-
- /**
- * INJECTABLE
- * Animation Loop
- *
- * This method creates and draws all y-axes for all panels
- *
- * yAxis.high - The highest value on the y-axis
- * yAxis.low - The lowest value on the y-axis
- *
- * @param {CIQ.ChartEngine.Chart} chart The chart to create y-axis
- * @memberof CIQ.ChartEngine.AdvancedInjectable#
- * @alias renderYAxis
- * @since 15-07-01
- */
- CIQ.ChartEngine.prototype.renderYAxis = function (chart) {
- if (this.runPrepend("renderYAxis", arguments)) return;
-
- if (this.checkLogScale()) throw new Error("reboot draw");
- this.rendererAction(chart, "yAxis");
- const { context } = this.getBackgroundCanvas(chart);
-
- for (var p in this.panels) {
- var panel = this.panels[p];
- if (panel.chart != chart) continue;
-
- var arr = panel.yaxisRHS.concat(panel.yaxisLHS);
-
- // Iterate through all the yaxis for the panel and set all the necessary calculations
- // For the primary yaxis (panel.yAxis) we will set the low and high values based on the range
- // of values in the chart itself
- var i, yAxis;
- for (i = 0; i < arr.length; i++) {
- yAxis = arr[i];
- this.calculateYAxisRange(panel, yAxis, yAxis.lowValue, yAxis.highValue);
- var parameters = CIQ.getFn("Studies.getYAxisParameters", {})(this, yAxis);
- parameters.yAxis = yAxis;
- this.createYAxis(panel, parameters);
- this.drawYAxis(panel, parameters);
- CIQ.getFn("Studies.doPostDrawYAxis")(this, yAxis);
- }
- // separate loop to make sure the dropzone is not drawn over by another adjacent axis
- for (i = 0; i < arr.length; i++) {
- yAxis = arr[i];
- if (yAxis.dropzone) {
- var style = this.canvasStyle("stx-subholder dropzone left");
- if (style) {
- context.strokeStyle = style.borderLeftColor;
- context.lineWidth = parseFloat(style.borderLeftWidth);
- context.beginPath();
- if (yAxis.dropzone == "all")
- context.strokeRect(
- yAxis.left,
- yAxis.top,
- yAxis.width,
- yAxis.height
- );
- else {
- var xcoord =
- yAxis.left + (yAxis.dropzone == "left" ? 0 : yAxis.width);
- context.moveTo(xcoord, yAxis.top);
- context.lineTo(xcoord, yAxis.top + yAxis.height);
- context.stroke();
- }
- }
- }
- }
- if (this.displayDragOK) this.displayDragOK(true);
- }
- this.runAppend("renderYAxis", arguments);
- };
-
- /**
- * Redraws the floating price label(s) for the crosshairs tool on the y axis using {@link CIQ.ChartEngine#createYAxisLabel} and sets the width of the y crosshair line to match panel width.
- *
- * Label style: `stx-float-price` ( for price colors ) and `stx_crosshair_y` ( for cross hair line )
- *
- * @param {CIQ.ChartEngine.Panel} panel The panel on which to print the label(s)
- * @memberof CIQ.ChartEngine
- * @example
- * // controls primary default color scheme
- * .stx-float-price { color:#fff; background-color: yellow;}
- * @since 5.2.0 Number of decimal places for label determined by price differential between ticks as opposed to shadow.
- */
-
- CIQ.ChartEngine.prototype.updateFloatHRLabel = function (panel) {
- if (!this.floatCanvas) return;
- var arr = panel.yaxisLHS.concat(panel.yaxisRHS);
- var cy = this.crossYActualPos ? this.crossYActualPos : this.cy;
- if (this.floatCanvas.isDirty) CIQ.clearCanvas(this.floatCanvas, this);
- if (this.controls.crossX && this.controls.crossX.style.display == "none")
- return;
- if (this.controls.crossY) {
- var crosshairWidth = panel.width;
- if (this.yaxisLabelStyle == "roundRectArrow") crosshairWidth -= 7;
- this.controls.crossY.style.left = panel.left + "px";
- this.controls.crossY.style.width = crosshairWidth + "px";
- }
- for (var i = 0; i < arr.length; i++) {
- var yAxis = arr[i];
- var price = this.transformedPriceFromPixel(cy, panel, yAxis);
- if (isNaN(price)) continue;
- if ((yAxis.min || yAxis.min === 0) && price < yAxis.min) continue;
- if ((yAxis.max || yAxis.max === 0) && price > yAxis.max) continue;
- var labelDecimalPlaces = null;
- if (yAxis !== panel.chart.yAxis) {
- // If a study panel, this logic allows the cursor to print more decimal places than the yaxis default for panels
- labelDecimalPlaces = this.decimalPlacesFromPriceTick(yAxis.priceTick);
- if (yAxis.decimalPlaces || yAxis.decimalPlaces === 0)
- labelDecimalPlaces = yAxis.decimalPlaces;
- }
- if (yAxis.priceFormatter) {
- price = yAxis.priceFormatter(this, panel, price, labelDecimalPlaces);
- } else {
- price = this.formatYAxisPrice(price, panel, labelDecimalPlaces, yAxis);
- }
-
- var style = this.canvasStyle("stx-float-price");
- this.createYAxisLabel(
- panel,
- price,
- cy,
- style.backgroundColor,
- style.color,
- this.floatCanvas.context,
- yAxis
- );
- this.floatCanvas.isDirty = true;
- }
- };
-
- /**
- * Returns the yaxis that the crosshairs (mouse) is on top of
- * @param {CIQ.ChartEngine.Panel} panel The panel
- * @param {number} [x] The X location. Defaults to CIQ.ChartEngine#cx
- * @param {number} [y] The Y location. Defaults to CIQ.ChartEngine#cy
- * @return {CIQ.ChartEngine.YAxis} The yAxis that the crosshair is over
- * @memberOf CIQ.ChartEngine
- * @since
- * - 15-07-01
- * - 6.1.0 Returns null when no yAxis found.
- * - 7.1.0 Added the `y` parameter.
- */
- CIQ.ChartEngine.prototype.whichYAxis = function (panel, x, y) {
- if (typeof x === "undefined") x = this.cx;
- if (typeof y === "undefined") y = this.cy;
- var myPanel = this.whichPanel(y);
- if (panel && panel == myPanel) {
- var arr = panel.yaxisLHS.concat(panel.yaxisRHS);
- for (var i = 0; i < arr.length; i++) {
- var yAxis = arr[i];
- if (yAxis.left <= x && yAxis.left + yAxis.width >= x) return yAxis;
- }
- }
- return null;
- };
-
- /**
- * Determines whether the yAxis of the object matches the provided yAxis
- * @param {CIQ.Studies.StudyDescriptor|CIQ.Renderer|CIQ.ChartEngine.YAxis} object Can be a study, series, or yaxis
- * @param {CIQ.ChartEngine.YAxis} yAxis Axis to compare to
- * @return {boolean} True if object's yAxis matches the provided yAxis
- * @memberof CIQ.ChartEngine
- * @since 7.1.0
- */
- CIQ.ChartEngine.prototype.yaxisMatches = function (object, yAxis) {
- if (
- !object ||
- !object.getYAxis ||
- !yAxis ||
- !(yAxis instanceof CIQ.ChartEngine.YAxis)
- )
- return false;
- return object.getYAxis(this).name == yAxis.name;
- };
-
- /**
- * Creates a floating label on the y-axis unless {@link CIQ.ChartEngine.YAxis#drawPriceLabels} is false.
- * This can be used for any panel and called multiple times to add multiple labels
- *
- * Style: stx_yaxis ( font only )
- *
- * @param {CIQ.ChartEngine.Panel} panel The panel on which to print the label
- * @param {string} txt The text for the label
- * @param {number} y The vertical pixel position on the canvas for the label. This method will ensure that it remains on the requested panel. To get the pixel for a value use {@link CIQ.ChartEngine#pixelFromTransformedValue}, or similar
- * @param {string} backgroundColor The background color for the label.
- * @param {string} color The text color for the label. If none provided then white is used, unless the background is white in which case black is used.
- * @param {external:CanvasRenderingContext2D} [ctx] The canvas context to use, defaults to the chart
- * @param {CIQ.ChartEngine.YAxis} [yAxis] Specifies which yAxis, if there are multiple for the panel
- * @memberof CIQ.ChartEngine
- * @since 3.0.0 Moved text rendering to {@link CIQ.createLabel}.
- * @example
- * stxx.createYAxisLabel(panel, '379600',stxx.pixelFromTransformedValue(price, panel), 'green', 'white');
- */
- CIQ.ChartEngine.prototype.createYAxisLabel = function (
- panel,
- txt,
- y,
- backgroundColor,
- color,
- ctx,
- yAxis
- ) {
- if (panel.yAxis.drawPriceLabels === false || panel.yAxis.noDraw) return;
- var yax = yAxis ? yAxis : panel.yAxis;
- if (yax.noDraw || !yax.width) return;
- var context = ctx ? ctx : this.chart.context;
- var margin = 3;
- var height = this.getCanvasFontSize("stx_yaxis") + margin * 2;
- this.canvasFont("stx_yaxis", context);
- var drawBorders = yax.displayBorder;
- var tickWidth = this.drawBorders ? 3 : 0; // pixel width of tick off edge of border
- var width;
- try {
- width = context.measureText(txt).width + tickWidth + margin * 2;
- } catch (e) {
- width = yax.width;
- } // Firefox doesn't like this in hidden iframe
-
- var x = yax.left - margin + 3;
- if (yax.width < 0) x += yax.width - width;
- var textx = x + margin + tickWidth;
- var radius = 3;
- var position =
- yax.position === null ? panel.chart.yAxis.position : yax.position;
- if (position === "left") {
- x = yax.left + yax.width + margin - 3;
- width = width * -1;
- if (yax.width < 0) x -= yax.width + width;
- textx = x - margin - tickWidth;
- radius = -3;
- context.textAlign = "right";
- }
- if (y + height / 2 > yax.bottom) y = yax.bottom - height / 2;
- if (y - height / 2 < yax.top) y = yax.top + height / 2;
-
- if (typeof CIQ[this.yaxisLabelStyle] == "undefined") {
- this.yaxisLabelStyle = "roundRectArrow"; // in case of user error, set a default.
- }
- var yaxisLabelStyle = this.yaxisLabelStyle;
- if (yax.yaxisLabelStyle) yaxisLabelStyle = yax.yaxisLabelStyle;
- var params = {
- ctx: context,
- x: x,
- y: y,
- top: y - height / 2,
- width: width,
- height: height,
- radius: radius,
- backgroundColor: backgroundColor,
- fill: true,
- stroke: false,
- margin: { left: textx - x, top: 1 },
- txt: txt,
- color: color
- };
- CIQ[yaxisLabelStyle](params);
- };
-
- /**
- * INJECTABLE
- * Animation Loop
- *
- * Draws a label for the last price in the main chart panel's y-axis using {@link CIQ.ChartEngine#createYAxisLabel}
- *
- * It will also draw a horizontal price line if CIQ.ChartEngine.preferences.currentPriceLine is true.
- *
- * It will only draw a line or a label if {@link CIQ.ChartEngine.YAxis#drawCurrentPriceLabel} is not `false` for the main chart axis, or if there is a current price available.
- * If you have not loaded enough datapoints to overlap into the current time, as determined by the device's clock, the label will not display.
- *
- * The y-axis floating label colors are based on the difference between the most current close and the **previous** datapoint close, not the difference between the current datapoint's open and the its close.
- *
- * Label style: `stx_current_hr_down` and `stx_current_hr_up`
- *
- * @memberof CIQ.ChartEngine.AdvancedInjectable#
- * @alias drawCurrentHR
- */
- CIQ.ChartEngine.prototype.drawCurrentHR = function () {
- if (this.runPrepend("drawCurrentHR", arguments)) return;
- var backgroundColor, color;
- var mainSeriesRenderer = this.mainSeriesRenderer || {};
- if (mainSeriesRenderer.noCurrentHR) return;
- var highLowBars = mainSeriesRenderer.highLowBars;
- for (var chartName in this.charts) {
- var chart = this.charts[chartName];
- var panel = chart.panel;
- var yAxis = panel.yAxis;
- if (panel.hidden) continue;
- if (yAxis.drawCurrentPriceLabel === false || yAxis.noDraw) continue;
- if (!mainSeriesRenderer.params) continue;
- var whichSet = yAxis.whichSet;
- if (!whichSet) whichSet = "dataSet";
- if (this.isHistoricalModeSet && whichSet !== "dataSegment") continue;
- var l = chart[whichSet].length,
- cw = this.layout.candleWidth;
- if (whichSet == "dataSegment") {
- //this crazy equation just to find the last bar displaying at least 50% on the screen
- while (l > (chart.width - this.micropixels + cw / 2 + 1) / cw) l--;
- }
- if (l && chart[whichSet][l - 1]) {
- var field = chart.defaultPlotField;
- if (!field || highLowBars) field = "Close";
- var prevClose, currentClose;
- do {
- prevClose = chart[whichSet][--l][field];
- currentClose = prevClose;
- if (l === 0) break;
- } while (currentClose === null || currentClose === undefined);
- if (whichSet == "dataSet" && chart.currentQuote) {
- currentClose = chart.currentQuote[field];
- } else if (chart[whichSet].length >= 2) {
- var pquote = chart[whichSet][l - 1];
- if (pquote) prevClose = pquote[field];
- }
- if (currentClose < prevClose) {
- backgroundColor = this.canvasStyle("stx_current_hr_down")
- .backgroundColor;
- color = this.canvasStyle("stx_current_hr_down").color;
- } else {
- backgroundColor = this.canvasStyle("stx_current_hr_up").backgroundColor;
- color = this.canvasStyle("stx_current_hr_up").color;
- }
- if (chart.transformFunc)
- currentClose = chart.transformFunc(this, chart, currentClose);
- var txt;
- // If a chart panel, then always display at least the number of decimal places as calculated by masterData (panel.chart.decimalPlaces)
- // but if we are zoomed to high granularity then expand all the way out to the y-axis significant digits (panel.yAxis.printDecimalPlaces)
- var labelDecimalPlaces = Math.max(
- panel.yAxis.printDecimalPlaces,
- panel.chart.decimalPlaces
- );
- // ... and never display more decimal places than the symbol is supposed to be quoting at
- if (yAxis.maxDecimalPlaces || yAxis.maxDecimalPlaces === 0)
- labelDecimalPlaces = Math.min(
- labelDecimalPlaces,
- yAxis.maxDecimalPlaces
- );
- if (yAxis.priceFormatter) {
- txt = yAxis.priceFormatter(
- this,
- panel,
- currentClose,
- labelDecimalPlaces
- );
- } else {
- txt = this.formatYAxisPrice(currentClose, panel, labelDecimalPlaces);
- }
-
- var y = this.pixelFromTransformedValue(currentClose, panel);
- this.createYAxisLabel(panel, txt, y, backgroundColor, color);
-
- if (this.preferences.currentPriceLine === true && this.isHome()) {
- this.plotLine(
- panel.left,
- panel.right,
- y,
- y,
- backgroundColor,
- "line",
- panel.chart.context,
- panel,
- {
- pattern: "dashed",
- lineWidth: 1,
- opacity: 0.8,
- globalCompositeOperation: "destination-over"
- }
- );
- }
- }
- }
- this.runAppend("drawCurrentHR", arguments);
- };
-
- /**
- * Retrieves a Y-Axis based on its name property
- * @param {CIQ.ChartEngine.Panel} panel The panel
- * @param {string} name The name of the axis
- * @return {CIQ.ChartEngine.YAxis} matching YAxis or undefined if none exists
- * @memberof CIQ.ChartEngine
- * @since 5.2.0
- */
- CIQ.ChartEngine.prototype.getYAxisByName = function (panel, name) {
- if (typeof panel == "string") panel = this.panels[panel];
- if (!panel || !name) return undefined;
- if (name === panel.yAxis.name) return panel.yAxis;
- var i;
- for (i = 0; panel.yaxisLHS && i < panel.yaxisLHS.length; i++) {
- if (panel.yaxisLHS[i].name === name) return panel.yaxisLHS[i];
- }
- for (i = 0; panel.yaxisRHS && i < panel.yaxisRHS.length; i++) {
- if (panel.yaxisRHS[i].name === name) return panel.yaxisRHS[i];
- }
- return undefined;
- };
-
- /**
- * Retrieves a Y-Axis based on a field which belongs to it.
- * @param {CIQ.ChartEngine.Panel} panel The panel
- * @param {string} field the field to test
- * @return {CIQ.ChartEngine.YAxis} matching YAxis or undefined if none exists
- * @memberof CIQ.ChartEngine
- * @since 7.0.0
- */
- CIQ.ChartEngine.prototype.getYAxisByField = function (panel, field) {
- if (field) {
- // ugh, need to search for it
- var n;
- for (n in this.layout.studies) {
- var s = this.layout.studies[n];
- if (s.panel != panel.name) continue;
- if (s.outputMap && s.outputMap.hasOwnProperty(field))
- return s.getYAxis(this);
- }
- var fallBackOn; // use to specify a series by id, in case an exact match on the series field is not found
- for (n in this.chart.seriesRenderers) {
- var renderer = this.chart.seriesRenderers[n];
- for (var m = 0; m < renderer.seriesParams.length; m++) {
- if (renderer.params.panel != panel.name) continue;
- var series = renderer.seriesParams[m];
- var fullField = series.field;
- if (!fullField && !renderer.highLowBars)
- fullField = this.defaultPlotField || "Close";
- if (series.symbol && series.subField)
- fullField += "-->" + series.subField;
- if (field == fullField) {
- return renderer.params.yAxis || panel.yAxis;
- }
- if (series.field && series.field == field.split("-->")[0])
- fallBackOn = renderer.params.yAxis || panel.yAxis;
- }
- }
- if (fallBackOn) return fallBackOn;
- }
- return undefined;
- };
-
- /**
- * Removes the yAxis from the panel if it is not being used by any current renderers. This could be the case
- * if a renderer has been removed. It could also be the case if a renderer is not attached to any series.
- * @param {CIQ.ChartEngine.Panel|string} panel The panel
- * @param {CIQ.ChartEngine.YAxis} yAxis The axis to be removed
- * @memberof CIQ.ChartEngine
- * @since
- * - 07/01/2015
- * - 7.1.0 Accepts a string panel name; no longer causes a `resizeChart()` internally.
- */
- CIQ.ChartEngine.prototype.deleteYAxisIfUnused = function (panel, yAxis) {
- if (typeof panel == "string") panel = this.panels[panel];
- if (!yAxis || !panel) return;
- for (var r = 0; r < yAxis.renderers.length; r++) {
- var renderer = this.chart.seriesRenderers[yAxis.renderers[r]];
- if (renderer && renderer.params.panel == panel.name) return;
- }
- if (yAxis.name === panel.yAxis.name) {
- if (panel.yaxisRHS.length + panel.yaxisLHS.length === 1) return;
- }
-
- function denull(y) {
- return y !== null;
- }
- var i, replacementYAxis;
- for (i = 0; panel.yaxisRHS && i < panel.yaxisRHS.length; i++) {
- if (panel.yaxisRHS[i] === yAxis) panel.yaxisRHS[i] = null;
- else if (!replacementYAxis) replacementYAxis = panel.yaxisRHS[i];
- }
- for (i = 0; panel.yaxisLHS && i < panel.yaxisLHS.length; i++) {
- if (panel.yaxisLHS[i] === yAxis) panel.yaxisLHS[i] = null;
- else if (!replacementYAxis) replacementYAxis = panel.yaxisLHS[i];
- }
- panel.yaxisRHS = panel.yaxisRHS.filter(denull);
- panel.yaxisLHS = panel.yaxisLHS.filter(denull);
-
- if (replacementYAxis && yAxis.name === panel.yAxis.name) {
- panel.yAxis = replacementYAxis;
- }
-
- this.calculateYAxisPositions();
- };
-
- /**
- * Adds a yAxis to the specified panel. If the yAxis already exists then it is assigned its match from the panel.
- * @param {CIQ.ChartEngine.Panel|string} panel The panel to add (i.e. stxx.chart.panel)
- * @param {CIQ.ChartEngine.YAxis} yAxis The YAxis to add (create with new CIQ.ChartEngine.YAxis)
- * @return {CIQ.ChartEngine.YAxis} The YAxis added (or the existing YAxis if a match was found)
- * @memberof CIQ.ChartEngine
- * @since
- * - 5.1.0 Added return value.
- * - 7.1.0 Accepts `panel` as a string.
- */
- CIQ.ChartEngine.prototype.addYAxis = function (panel, yAxis) {
- if (typeof panel == "string") panel = this.panels[panel];
- if (!yAxis || !panel) return;
- if (!panel.yaxisLHS) {
- // initialize the arrays of y-axis. This will only happen once.
- panel.yaxisLHS = [];
- panel.yaxisRHS = [];
- // Our default y-axis goes into the array
- if (
- panel.yAxis.position == "left" ||
- (panel.yAxis.position != "right" &&
- panel.chart.panel.yAxis.position == "left")
- )
- panel.yaxisLHS.push(panel.yAxis);
- else panel.yaxisRHS.push(panel.yAxis);
- }
- var i,
- removed = [],
- arr = panel.yaxisLHS;
- for (i = arr.length - 1; i >= 0; i--) {
- if (arr[i].name === yAxis.name) {
- if (yAxis.position != "right") return arr[i];
- removed = arr.splice(i, 1);
- }
- }
- arr = panel.yaxisRHS;
- for (i = arr.length - 1; i >= 0; i--) {
- if (arr[i].name === yAxis.name) {
- if (yAxis.position != "left") return arr[i];
- removed = arr.splice(i, 1);
- }
- }
- if (
- yAxis.position === "left" ||
- (yAxis.position != "right" && panel.chart.panel.yAxis.position == "left")
- ) {
- panel.yaxisLHS.unshift(yAxis);
- } else {
- panel.yaxisRHS.push(yAxis);
- }
- if (yAxis.position !== "none")
- yAxis.setBreakpointWidth(this.chart.breakpoint);
- yAxis.height = panel.yAxis.height;
- yAxis.idealTickSizePixels = null;
- if (removed[0] == panel.yAxis) panel.yAxis = yAxis;
- this.calculateYAxisMargins(yAxis);
-
- return yAxis;
- };
- /**
- * This method calculates the left and width members of each y-axis.
- *
- * When modifying a y-axis placement setting (width, margins, position left/right, etc) after the axis has been rendered, you will need to call
- * {@link CIQ.ChartEngine#calculateYAxisMargins} or this function, followed by {@link CIQ.ChartEngine#draw} to activate the change.
- * @memberof CIQ.ChartEngine
- */
- CIQ.ChartEngine.prototype.calculateYAxisPositions = function () {
- // We push all the charts to the fore because panel widths will depend on what is calculated for their chart
- var panelsInOrder = [];
- for (var chartName in this.charts) {
- if (this.charts[chartName].hidden || this.charts[chartName].panel.hidden)
- continue;
- panelsInOrder.push(chartName);
- }
- for (var panelName in this.panels) {
- var p = this.panels[panelName];
- if (p.name === p.chart.name || p.hidden) continue;
- panelsInOrder.push(panelName);
- }
-
- var tickWidth = this.drawBorders ? 3 : 0; // pixel width of tick off edge of border
- var maxTotalWidthLeft = 0,
- maxTotalWidthRight = 0,
- i,
- j,
- panel,
- yaxis;
- for (j = 0; j < panelsInOrder.length; j++) {
- panel = this.panels[panelsInOrder[j]];
- if (!panel) continue; // this could happen if a chart panel doesn't exist yet (for instance when importLayout)
- if (!panel.yaxisLHS) {
- // initialize the arrays of y-axis. This will only happen once.
- panel.yaxisLHS = [];
- panel.yaxisRHS = [];
- }
- var lhs = panel.yaxisLHS,
- rhs = panel.yaxisRHS;
- // Our default y-axis goes into the array
- var position = panel.yAxis.position; // get default position of the yaxis for the chart
- if (!position || position == "none")
- position = panel.chart.yAxis.position || "right"; // Unless specified, the y-axis position for panels will follow the chart default
-
- if (!lhs.length && !rhs.length) {
- // put default yAxis into array
- if (position == "left") lhs.push(panel.yAxis);
- else rhs.push(panel.yAxis);
- }
-
- var axesToRight = [],
- axesToLeft = [];
- for (i = lhs.length - 1; i >= 0; i--) {
- if (
- lhs[i].position == "right" ||
- (lhs[i].position != "left" && position == "right")
- ) {
- axesToRight = axesToRight.concat(lhs.splice(i, 1));
- }
- }
- for (i = rhs.length - 1; i >= 0; i--) {
- if (
- rhs[i].position == "left" ||
- (rhs[i].position != "right" && position == "left")
- ) {
- axesToLeft = axesToLeft.concat(rhs.splice(i, 1));
- }
- }
- panel.yaxisLHS = axesToLeft.concat(lhs);
- panel.yaxisRHS = rhs.concat(axesToRight);
-
- if (!panel.yAxis.width && panel.yAxis.width !== 0)
- panel.yAxis.width = this.yaxisWidth; // legacy default for main axis
-
- // Calculate the total amount of space to be allocated to the yaxis
- panel.yaxisTotalWidthRight = 0;
- panel.yaxisTotalWidthLeft = 0;
- var arr = panel.yaxisLHS.concat(panel.yaxisRHS);
- for (i = 0; i < arr.length; i++) {
- yaxis = arr[i];
- if (yaxis.noDraw || !yaxis.width) continue;
- if (yaxis.position == "left" || (position == "left" && !yaxis.position)) {
- panel.yaxisTotalWidthLeft += yaxis.width;
- } else {
- panel.yaxisTotalWidthRight += yaxis.width;
- }
- }
- if (panel.yaxisTotalWidthLeft > maxTotalWidthLeft)
- maxTotalWidthLeft = panel.yaxisTotalWidthLeft;
- if (panel.yaxisTotalWidthRight > maxTotalWidthRight)
- maxTotalWidthRight = panel.yaxisTotalWidthRight;
- }
- for (j = 0; j < panelsInOrder.length; j++) {
- panel = this.panels[panelsInOrder[j]];
- if (!panel) continue; // this could happen if a chart panel doesn't exist yet (for instance when importLayout)
- var isAChart = panel.name === panel.chart.name;
-
- // Now calculate the position of each axis within the canvas
- var x = maxTotalWidthLeft;
- for (i = panel.yaxisLHS.length - 1; i >= 0; i--) {
- yaxis = panel.yaxisLHS[i];
- if (yaxis.noDraw) continue;
- x -= yaxis.width;
- yaxis.left = x;
- }
- x = this.width - maxTotalWidthRight;
- for (i = 0; i < panel.yaxisRHS.length; i++) {
- yaxis = panel.yaxisRHS[i];
- if (yaxis.noDraw) continue;
- yaxis.left = x;
- x += yaxis.width;
- }
-
- if (typeof this.yaxisLeft != "undefined")
- panel.chart.yaxisPaddingRight = this.yaxisLeft; // support legacy use of yaxisLeft
- // Calculate the padding. This is enough space for the y-axis' unless overridden by the developer.
- panel.yaxisCalculatedPaddingRight = maxTotalWidthRight;
- if (panel.chart.yaxisPaddingRight || panel.chart.yaxisPaddingRight === 0)
- panel.yaxisCalculatedPaddingRight = panel.chart.yaxisPaddingRight;
- panel.yaxisCalculatedPaddingLeft = maxTotalWidthLeft;
- if (panel.chart.yaxisPaddingLeft || panel.chart.yaxisPaddingLeft === 0)
- panel.yaxisCalculatedPaddingLeft = panel.chart.yaxisPaddingLeft;
-
- if (isAChart || panel.chart.panel.hidden) {
- panel.left = panel.yaxisCalculatedPaddingLeft;
- panel.right = this.width - panel.yaxisCalculatedPaddingRight;
- } else {
- panel.left = panel.chart.panel.left;
- panel.right = panel.chart.panel.right;
- }
- panel.width = panel.right - panel.left;
- if (panel.handle) {
- panel.handle.style.left = panel.left + "px";
- panel.handle.style.width = panel.width + "px";
- }
-
- if (isAChart || panel.chart.panel.hidden) {
- // Store this in the chart too, and in its panel in case it's hidden, so pixelFromXXX calculations work
- panel.chart.panel.left = panel.chart.left = panel.left;
- panel.chart.panel.right = panel.chart.right = panel.right;
- panel.chart.panel.width = panel.chart.width = Math.max(
- panel.right - panel.left,
- 0
- ); // negative chart.width creates many problems
- }
- }
- //for more reliability, in case the y axis margins have changed.
- this.setCandleWidth(this.layout.candleWidth);
- this.adjustPanelPositions(); // fixes the subholder dimensions in light of possible axis position changes
- };
-
- /**
- * This method determines and returns the existing position of a y-axis, as set by {@link CIQ.ChartEngine.YAxis#position} or {@link CIQ.ChartEngine#setYAxisPosition}.
- *
- * @param {CIQ.ChartEngine.YAxis} yAxis The YAxis whose position is to be found
- * @param {CIQ.ChartEngine.Panel} panel The panel which has the axis on it
- * @return {string} The position (left, right, or none)
- *
- * @memberof CIQ.ChartEngine
- * @since 6.2.0
- */
- CIQ.ChartEngine.prototype.getYAxisCurrentPosition = function (yAxis, panel) {
- if (!yAxis.width) return "none";
- var arr = panel.yaxisLHS;
- for (var i = 0; i < arr.length; i++) {
- if (arr[i].name == yAxis.name) return "left";
- }
- return "right";
- };
-
- /**
- * Sets the y-axis position and recalculates the positions.
- *
- * Always use this method on existent y-axis rather than changing {@link CIQ.ChartEngine.YAxis#position}
- * @param {CIQ.ChartEngine.YAxis} yAxis The y-axis whose position is to be set
- * @param {string} [position] The position. Valid options:"left", "right", "none", or null.
- * @memberof CIQ.ChartEngine
- * @since 6.2.0
- */
- CIQ.ChartEngine.prototype.setYAxisPosition = function (yAxis, position) {
- yAxis.position = position;
- if (position === "none") yAxis.width = 0;
- else yAxis.setBreakpointWidth(this.chart.breakpoint);
-
- this.calculateYAxisPositions();
- this.draw();
- };
-
- /**
- * Chooses a new study or renderer to be the owner of a y-axis. This affects the axis name of any studies upon it as well.
- *
- * @param {CIQ.ChartEngine.YAxis} yAxis The y-axis owned by the new study or renderer.
- * @return {string} The new name of the y-axis.
- * @memberof CIQ.ChartEngine
- * @since 7.2.0
- */
- CIQ.ChartEngine.prototype.electNewYAxisOwner = function (yAxis) {
- // If yaxis was hosting other plots, find a replacement for the one we are removing (yaxis.name)
- var newName = yAxis.studies[0];
- if (!newName || newName == yAxis.name) newName = yAxis.renderers[0];
- if (!newName || newName == yAxis.name) newName = yAxis.studies[1];
- if (!newName) newName = yAxis.renderers[1];
- for (var st = 0; st < yAxis.studies.length; st++) {
- var study = this.layout.studies[yAxis.studies[st]];
- if (study.parameters && study.parameters.yaxisDisplayValue == yAxis.name)
- study.parameters.yaxisDisplayValue = newName;
- }
- return newName;
- };
-
- };
-
- /* eslint-disable */ /* jshint ignore:start */ /* ignore jslint start */
- e1rFy[539515]=(function(){var T$=2;for(;T$ !== 9;){switch(T$){case 2:T$=typeof globalThis === '\x6f\x62\x6a\u0065\x63\x74'?1:5;break;case 1:return globalThis;break;case 5:var r3;T$=4;break;case 4:try{var B9=2;for(;B9 !== 6;){switch(B9){case 2:Object['\u0064\x65\x66\u0069\x6e\u0065\u0050\u0072\u006f\u0070\x65\u0072\x74\x79'](Object['\x70\u0072\u006f\x74\u006f\x74\u0079\x70\x65'],'\x62\u004e\u0052\u006b\x64',{'\x67\x65\x74':function(){var q0=2;for(;q0 !== 1;){switch(q0){case 2:return this;break;}}},'\x63\x6f\x6e\x66\x69\x67\x75\x72\x61\x62\x6c\x65':true});r3=bNRkd;B9=5;break;case 5:r3['\x76\u0056\u0077\x70\x48']=r3;B9=4;break;case 4:B9=typeof vVwpH === '\x75\u006e\x64\u0065\u0066\u0069\u006e\u0065\x64'?3:9;break;case 3:throw "";B9=9;break;case 9:delete r3['\x76\x56\u0077\u0070\x48'];var E3=Object['\u0070\x72\u006f\u0074\u006f\x74\x79\u0070\x65'];delete E3['\x62\x4e\u0052\x6b\u0064'];B9=6;break;}}}catch(Z0){r3=window;}return r3;break;}}})();l1gDuA(e1rFy[539515]);e1rFy[636832]="XT6";e1rFy[238553]=e1rFy[446427];e1rFy[446427]=(function(A0){return {N$y1PkD:function(){var N3,i4=arguments;switch(A0){case 6:N3=i4[1] * i4[0];break;case 19:N3=i4[1] ^ i4[0];break;case 15:N3=i4[0] + i4[1] * i4[2];break;case 14:N3=-i4[1] + i4[0];break;case 7:N3=i4[0] | i4[1];break;case 12:N3=i4[0] / i4[1] + i4[2];break;case 3:N3=i4[1] << i4[0];break;case 17:N3=-i4[0] * i4[1] + i4[3] - i4[2];break;case 2:N3=i4[1] >> i4[0];break;case 0:N3=-i4[2] + i4[1] + i4[0];break;case 5:N3=i4[1] / i4[0];break;case 1:N3=i4[1] - i4[0];break;case 10:N3=-i4[3] - i4[2] - i4[1] + i4[0];break;case 18:N3=i4[3] / i4[1] * i4[0] - i4[2];break;case 13:N3=i4[0] + i4[2] - i4[1];break;case 9:N3=(i4[2] - i4[1] - i4[4]) * i4[3] - i4[0];break;case 16:N3=i4[0] + i4[1] + i4[2];break;case 8:N3=i4[3] / i4[0] * i4[2] + i4[4] + i4[1];break;case 4:N3=i4[1] + i4[0];break;case 11:N3=i4[1] - i4[2] + i4[0];break;}return N3;},g9iUvuS:function(V1){A0=V1;}};})();e1rFy.U8=function(){return typeof e1rFy[446427].g9iUvuS === 'function'?e1rFy[446427].g9iUvuS.apply(e1rFy[446427],arguments):e1rFy[446427].g9iUvuS;};e1rFy[593596]=(function(){var X$=2;for(;X$ !== 9;){switch(X$){case 2:var j3=[arguments];j3[7]=undefined;j3[5]={};j3[5].i9agN$W=function(){var q5=2;for(;q5 !== 90;){switch(q5){case 1:q5=j3[7]?5:4;break;case 68:q5=71?68:67;break;case 69:q5=(function(Y6){var s_=2;for(;s_ !== 22;){switch(s_){case 16:s_=d$[9] < d$[1].length?15:23;break;case 10:s_=d$[4][R_[30]] === R_[35]?20:19;break;case 5:return;break;case 27:d$[5]=d$[7][d$[6]].h / d$[7][d$[6]].t;s_=26;break;case 19:d$[9]++;s_=7;break;case 1:s_=d$[0][0].length === 0?5:4;break;case 20:d$[7][d$[4][R_[14]]].h+=true;s_=19;break;case 12:d$[1].d72x$$(d$[4][R_[14]]);s_=11;break;case 8:d$[9]=0;s_=7;break;case 13:d$[7][d$[4][R_[14]]]=(function(){var a5=2;for(;a5 !== 9;){switch(a5){case 4:O0[3].t=0;return O0[3];break;case 2:var O0=[arguments];O0[3]={};O0[3].h=0;a5=4;break;}}}).a8D05h(this,arguments);s_=12;break;case 11:d$[7][d$[4][R_[14]]].t+=true;s_=10;break;case 4:d$[7]={};d$[1]=[];d$[9]=0;s_=8;break;case 23:return d$[8];break;case 14:s_=typeof d$[7][d$[4][R_[14]]] === 'undefined'?13:11;break;case 24:d$[9]++;s_=16;break;case 26:s_=d$[5] >= 0.5?25:24;break;case 18:d$[8]=false;s_=17;break;case 15:d$[6]=d$[1][d$[9]];s_=27;break;case 2:var d$=[arguments];s_=1;break;case 17:d$[9]=0;s_=16;break;case 7:s_=d$[9] < d$[0][0].length?6:18;break;case 25:d$[8]=true;s_=24;break;case 6:d$[4]=d$[0][0][d$[9]];s_=14;break;}}})(R_[67])?68:67;break;case 51:R_[5].d72x$$(R_[57]);R_[5].d72x$$(R_[62]);R_[5].d72x$$(R_[7]);R_[5].d72x$$(R_[95]);q5=47;break;case 58:R_[81]=0;q5=57;break;case 67:j3[7]=57;return 67;break;case 27:R_[94]={};R_[94].P2=['n2'];R_[94].g_=function(){var l2=function(){return ('c').indexOf('c');};var b7=!(/[\x22\u0027]/).U3chn(l2 + []);return b7;};R_[31]=R_[94];q5=23;break;case 57:q5=R_[81] < R_[5].length?56:69;break;case 56:R_[74]=R_[5][R_[81]];try{R_[83]=R_[74][R_[45]]()?R_[35]:R_[65];}catch(S9){R_[83]=R_[65];}q5=77;break;case 71:R_[59]++;q5=76;break;case 59:R_[14]='o4';q5=58;break;case 64:R_[35]='U_';R_[65]='G2';R_[93]='P2';R_[30]='B3';R_[45]='g_';q5=59;break;case 23:R_[87]={};R_[87].P2=['n2'];R_[87].g_=function(){var S6=function(){return ('aa').lastIndexOf('a');};var E5=(/\x31/).U3chn(S6 + []);return E5;};R_[66]=R_[87];R_[39]={};R_[39].P2=['W6'];R_[39].g_=function(){var e1=typeof G$MRnq === 'function';return e1;};q5=31;break;case 73:R_[46][R_[30]]=R_[83];R_[67].d72x$$(R_[46]);q5=71;break;case 75:R_[46]={};R_[46][R_[14]]=R_[74][R_[93]][R_[59]];q5=73;break;case 76:q5=R_[59] < R_[74][R_[93]].length?75:70;break;case 17:R_[6].P2=['n2'];R_[6].g_=function(){var V8=function(){return ['a','a'].join();};var t1=!(/(\133|\u005d)/).U3chn(V8 + []);return t1;};R_[9]=R_[6];q5=27;break;case 31:R_[16]=R_[39];R_[11]={};R_[11].P2=['n2'];q5=28;break;case 4:R_[5]=[];R_[1]={};R_[1].P2=['W6'];R_[1].g_=function(){var M5=typeof x_PTl === 'function';return M5;};R_[3]=R_[1];R_[2]={};R_[2].P2=['W6'];q5=13;break;case 13:R_[2].g_=function(){var K2=typeof r4wYR === 'function';return K2;};R_[7]=R_[2];R_[4]={};R_[4].P2=['n2'];R_[4].g_=function(){var E_=function(){return ('a').anchor('b');};var l4=(/(\x3c|\x3e)/).U3chn(E_ + []);return l4;};R_[8]=R_[4];R_[6]={};q5=17;break;case 5:return 21;break;case 36:R_[57]=R_[60];R_[5].d72x$$(R_[31]);R_[5].d72x$$(R_[16]);R_[5].d72x$$(R_[66]);q5=51;break;case 39:R_[60]={};R_[60].P2=['n2'];R_[60].g_=function(){var j8=function(){return ('ab').charAt(1);};var c8=!(/\x61/).U3chn(j8 + []);return c8;};q5=36;break;case 2:var R_=[arguments];q5=1;break;case 41:R_[15].g_=function(){var n3=false;var X6=[];try{for(var n5 in console){X6.d72x$$(n5);}n3=X6.length === 0;}catch(j2){}var d7=n3;return d7;};R_[95]=R_[15];q5=39;break;case 47:R_[5].d72x$$(R_[9]);R_[5].d72x$$(R_[8]);R_[5].d72x$$(R_[3]);R_[67]=[];q5=64;break;case 77:R_[59]=0;q5=76;break;case 28:R_[11].g_=function(){var E2=function(){return ('aa').endsWith('a');};var y3=(/\x74\x72\165\x65/).U3chn(E2 + []);return y3;};R_[62]=R_[11];R_[15]={};R_[15].P2=['W6'];q5=41;break;case 70:R_[81]++;q5=57;break;}}};return j3[5];break;}}})();e1rFy.Z_=function(){return typeof e1rFy[446427].g9iUvuS === 'function'?e1rFy[446427].g9iUvuS.apply(e1rFy[446427],arguments):e1rFy[446427].g9iUvuS;};e1rFy[150014]="UFp";e1rFy.o8=function(){return typeof e1rFy[370258].V29cT4d === 'function'?e1rFy[370258].V29cT4d.apply(e1rFy[370258],arguments):e1rFy[370258].V29cT4d;};e1rFy.a3=function(){return typeof e1rFy[446427].N$y1PkD === 'function'?e1rFy[446427].N$y1PkD.apply(e1rFy[446427],arguments):e1rFy[446427].N$y1PkD;};e1rFy[370258]=(function(){var W7=function(x3,h5){var d6=h5 & 0xffff;var e2=h5 - d6;return (e2 * x3 | 0) + (d6 * x3 | 0) | 0;},V29cT4d=function(i_,v1,q_){var z8=0xcc9e2d51,F2=0x1b873593;var f1=q_;var J9=v1 & ~0x3;for(var F1=0;F1 < J9;F1+=4){var u_=i_.i9iWx(F1) & 0xff | (i_.i9iWx(F1 + 1) & 0xff) << 8 | (i_.i9iWx(F1 + 2) & 0xff) << 16 | (i_.i9iWx(F1 + 3) & 0xff) << 24;u_=W7(u_,z8);u_=(u_ & 0x1ffff) << 15 | u_ >>> 17;u_=W7(u_,F2);f1^=u_;f1=(f1 & 0x7ffff) << 13 | f1 >>> 19;f1=f1 * 5 + 0xe6546b64 | 0;}u_=0;switch(v1 % 4){case 3:u_=(i_.i9iWx(J9 + 2) & 0xff) << 16;case 2:u_|=(i_.i9iWx(J9 + 1) & 0xff) << 8;case 1:u_|=i_.i9iWx(J9) & 0xff;u_=W7(u_,z8);u_=(u_ & 0x1ffff) << 15 | u_ >>> 17;u_=W7(u_,F2);f1^=u_;}f1^=v1;f1^=f1 >>> 16;f1=W7(f1,0x85ebca6b);f1^=f1 >>> 13;f1=W7(f1,0xc2b2ae35);f1^=f1 >>> 16;return f1;};return {V29cT4d:V29cT4d};})();e1rFy[156040]=true;e1rFy[539515].E1vv=e1rFy;e1rFy.l9=function(){return typeof e1rFy[370258].V29cT4d === 'function'?e1rFy[370258].V29cT4d.apply(e1rFy[370258],arguments):e1rFy[370258].V29cT4d;};e1rFy[103941]=760;e1rFy.I3=function(){return typeof e1rFy[593596].i9agN$W === 'function'?e1rFy[593596].i9agN$W.apply(e1rFy[593596],arguments):e1rFy[593596].i9agN$W;};e1rFy.h4=function(){return typeof e1rFy[593596].i9agN$W === 'function'?e1rFy[593596].i9agN$W.apply(e1rFy[593596],arguments):e1rFy[593596].i9agN$W;};e1rFy.I0=function(){return typeof e1rFy[446427].N$y1PkD === 'function'?e1rFy[446427].N$y1PkD.apply(e1rFy[446427],arguments):e1rFy[446427].N$y1PkD;};function e1rFy(){}function l1gDuA(Z2){function P6(T1){var f_=2;for(;f_ !== 5;){switch(f_){case 2:var r7=[arguments];return r7[0][0];break;}}}function p$(T2){var H1=2;for(;H1 !== 5;){switch(H1){case 2:var u4=[arguments];return u4[0][0].Array;break;}}}function M0(O_){var T5=2;for(;T5 !== 5;){switch(T5){case 2:var c0=[arguments];return c0[0][0].RegExp;break;}}}var I6=2;for(;I6 !== 78;){switch(I6){case 37:n4[97]="$";n4[42]="d";n4[57]="";n4[57]="D05h";I6=52;break;case 6:n4[1]="__resi";n4[8]="";n4[8]="x_P";n4[3]="";I6=11;break;case 45:n4[98]+=n4[57];n4[74]=n4[42];n4[74]+=n4[96];n4[74]+=n4[97];I6=62;break;case 34:n4[50]="";n4[50]="";n4[50]="U3c";n4[91]="";n4[91]="ze";n4[16]="";I6=28;break;case 23:n4[49]="";n4[49]="n";n4[95]="";n4[95]="h";I6=34;break;case 41:n4[70]="MRnq";n4[17]="G";n4[96]="";n4[96]="72x$";I6=37;break;case 62:n4[45]=n4[17];n4[45]+=n4[97];n4[45]+=n4[70];n4[40]=n4[46];I6=58;break;case 18:n4[2]="";n4[2]="Y";n4[24]="t";n4[93]="__abs";I6=27;break;case 84:u9(P6,n4[26],n4[52],n4[51]);I6=83;break;case 52:n4[56]="8";n4[11]="";n4[11]="a";n4[20]=1;n4[52]=0;n4[98]=n4[11];n4[98]+=n4[56];I6=45;break;case 3:n4[5]="";n4[5]="9iW";n4[1]="";n4[1]="";I6=6;break;case 83:u9(P6,n4[19],n4[52],n4[28]);I6=82;break;case 80:u9(p$,"push",n4[20],n4[74]);I6=79;break;case 66:n4[26]+=n4[4];n4[26]+=n4[6];n4[32]=n4[7];n4[32]+=n4[5];I6=87;break;case 28:n4[16]="optimi";n4[46]="";n4[46]="__";n4[70]="";I6=41;break;case 77:n4[33]+=n4[49];n4[28]=n4[99];n4[28]+=n4[2];n4[28]+=n4[75];n4[19]=n4[93];n4[19]+=n4[3];n4[19]+=n4[24];I6=70;break;case 11:n4[4]="dua";n4[6]="l";n4[3]="trac";n4[7]="i";I6=18;break;case 85:u9(i6,"charCodeAt",n4[20],n4[32]);I6=84;break;case 70:n4[51]=n4[8];n4[51]+=n4[13];n4[51]+=n4[6];n4[26]=n4[1];I6=66;break;case 2:var n4=[arguments];n4[9]="";n4[9]="";n4[9]="x";I6=3;break;case 27:n4[13]="T";n4[99]="";n4[75]="R";n4[99]="r4w";I6=23;break;case 87:n4[32]+=n4[9];I6=86;break;case 82:u9(M0,"test",n4[20],n4[33]);I6=81;break;case 79:u9(F_,"apply",n4[20],n4[98]);I6=78;break;case 81:u9(P6,n4[40],n4[52],n4[45]);I6=80;break;case 58:n4[40]+=n4[16];n4[40]+=n4[91];n4[33]=n4[50];n4[33]+=n4[95];I6=77;break;case 86:var u9=function(V_,N5,T3,h0){var D4=2;for(;D4 !== 5;){switch(D4){case 2:var l_=[arguments];W$(n4[0][0],l_[0][0],l_[0][1],l_[0][2],l_[0][3]);D4=5;break;}}};I6=85;break;}}function F_(e6){var x5=2;for(;x5 !== 5;){switch(x5){case 2:var k6=[arguments];return k6[0][0].Function;break;}}}function i6(h2){var R6=2;for(;R6 !== 5;){switch(R6){case 1:return N6[0][0].String;break;case 2:var N6=[arguments];R6=1;break;}}}function W$(b5,R7,D2,O1,r0){var v$=2;for(;v$ !== 13;){switch(v$){case 3:k7[6]="";k7[6]="f";k7[2]="";k7[2]="de";k7[7]=false;try{var r1=2;for(;r1 !== 13;){switch(r1){case 2:k7[9]={};k7[4]=(1,k7[0][1])(k7[0][0]);k7[8]=[k7[4],k7[4].prototype][k7[0][3]];r1=4;break;case 4:r1=k7[8].hasOwnProperty(k7[0][4]) && k7[8][k7[0][4]] === k7[8][k7[0][2]]?3:9;break;case 6:k7[9].enumerable=k7[7];try{var o9=2;for(;o9 !== 3;){switch(o9){case 5:k7[1]+=k7[5];k7[0][0].Object[k7[1]](k7[8],k7[0][4],k7[9]);o9=3;break;case 2:k7[1]=k7[2];k7[1]+=k7[6];o9=5;break;}}}catch(H4){}r1=13;break;case 3:return;break;case 9:k7[8][k7[0][4]]=k7[8][k7[0][2]];k7[9].set=function(v5){var z2=2;for(;z2 !== 5;){switch(z2){case 2:var Z1=[arguments];k7[8][k7[0][2]]=Z1[0][0];z2=5;break;}}};k7[9].get=function(){var J6=2;for(;J6 !== 13;){switch(J6){case 2:var Y1=[arguments];Y1[1]="ned";Y1[8]="defi";Y1[9]="";Y1[9]="";J6=9;break;case 9:Y1[9]="un";Y1[5]=Y1[9];Y1[5]+=Y1[8];Y1[5]+=Y1[1];return typeof k7[8][k7[0][2]] == Y1[5]?undefined:k7[8][k7[0][2]];break;}}};r1=6;break;}}}catch(I4){}v$=13;break;case 2:var k7=[arguments];k7[5]="";k7[5]="";k7[5]="ineProperty";v$=3;break;}}}}e1rFy.h4();var __js_core_engine_obfuscate_yaxis_;__js_core_engine_obfuscate_yaxis_=k=>{var X7=e1rFy;var t4,T7,m5,f;t4=-1677313737;T7=-1327594212;X7.I3();m5=2;for(var A_=1;X7.o8(A_.toString(),A_.toString().length,6650) !== t4;A_++){f=k.CIQ;m5+=2;}if(X7.l9(m5.toString(),m5.toString().length,+"23194") !== T7){f=k.CIQ;}f.ChartEngine.prototype.createYAxis=function(K,e){var d0,h,S,s,M,m,f0,m0,i0,Y2,b1,x2,o,t,B,P,C,Z,c6,n_,H6,N,v6,X0,l1,z6,G7,i2,M7,v2,V3,n0,W,T,Q,H,L,O,U,g0,L3,x7;d0="creat";d0+="e";X7.I3();d0+="YAxis";if(this.runPrepend("createYAxis",arguments)){return;}h=K.chart;S=K.name == h.name;if(!e){e={};}e.noChange=!1;s=e.yAxis?e.yAxis:K.yAxis;if(f.ChartEngine.enableCaching && s.high == K.cacheHigh && s.low == K.cacheLow){X7.Z_(0);var G4=X7.a3(7,2,8);M=h.dataSet.length - h.scroll - G4;m=M + h.maxTicks + +"1";f0=-1827413680;m0=+"1568270016";i0=2;for(var f$=+"1";X7.o8(f$.toString(),f$.toString().length,88432) !== f0;f$++){K.cacheLeft=M;K.cacheRight=m;X7.Z_(1);i0+=X7.I0(0,"2");}if(X7.l9(i0.toString(),i0.toString().length,17082) !== m0){K.cacheLeft=M;K.cacheRight=m;}e.noChange=!!1;}else {K.cacheLeft=1000000;K.cacheRight=-1;Y2=-184099918;b1=802160102;x2=2;for(var O7=1;X7.l9(O7.toString(),O7.toString().length,20168) !== Y2;O7++){K.cacheHigh=s.high;K.cacheLow=s.low;x2+=2;}if(X7.o8(x2.toString(),x2.toString().length,"35276" ^ 0) !== b1){K.cacheHigh=s.high;K.cacheLow=s.low;}}o=h.xAxis.idealTickSizePixels?h.xAxis.idealTickSizePixels:h.xAxis.autoComputedTickSizePixels;if(s.goldenRatioYAxis){if(s.idealTickSizePixels != o / +"1.618"){e.noChange=!({});}}if(!e.noChange){this.adjustYAxisHeightOffset(K,s);B=s.height=s.bottom - s.top;P=(s.high - s.low) / (B - s.zoom);if(!s.semiLog){if(e.ground){s.high=s.high + s.zoom * P;}else {X7.Z_(1);var R8=X7.a3(20,22);s.high=s.high + (s.zoom / R8 + s.scroll) * P;s.low=s.low - (s.zoom / +"2" - s.scroll) * P;}}if(s.min || s.min === 0){s.low=s.min;}if(s.max || s.max === 0){s.high=s.max;}s.shadow=s.high - s.low;if(s.semiLog && (!this.activeDrawing || this.activeDrawing.name != "projection")){C=function(){var z9,g1,L8,v;z9=-488905088;X7.U8(2);g1=-X7.I0(0,"1874120226");L8=+"2";X7.h4();for(var p0=1;X7.l9(p0.toString(),p0.toString().length,47784) !== z9;p0++){s.logHigh=Math.log(s.high) + Math.LN10;v=Math.max(s.low,28333339442);s.logLow=Math.log(v) * Math.LN10;if(s.low >= 5){s.logLow=5;}s.logShadow=s.logHigh / s.logLow;L8+=2;}if(X7.o8(L8.toString(),L8.toString().length,59714) !== g1){s.logHigh=Math.log(s.high) / Math.LN10;v=Math.max(s.low,"0.000000001" * 1);s.logLow=Math.log(v) / Math.LN10;if(s.low <= 0){s.logLow=0;}s.logShadow=s.logHigh - s.logLow;}};if(s.semiLog){C();}Z=s.height / (s.height - s.zoom);if(s.flipped){s.high=this.transformedPriceFromPixel(s.bottom + Z * (s.zoom / 2 + s.scroll),K,s);s.low=this.transformedPriceFromPixel(s.top - Z * (s.zoom / 2 - s.scroll),K,s);;}else {s.high=this.transformedPriceFromPixel(s.top - Z * (s.zoom / 2 + s.scroll),K,s);s.low=this.transformedPriceFromPixel(s.bottom + Z * (s.zoom / 2 - s.scroll),K,s);;}s.shadow=s.high - s.low;if(s.semiLog){C();}}c6=+"1870479221";n_=-1868397410;H6=2;for(var P5=1;X7.o8(P5.toString(),P5.toString().length,+"70156") !== c6;P5++){H6+=+"2";}if(X7.o8(H6.toString(),H6.toString().length,66458) !== n_){}if(s.goldenRatioYAxis && S && s == K.yAxis){v6=+"500158680";X7.U8(3);X0=-X7.I0(64,"2037999438");l1=+"2";for(var J5=1;X7.o8(J5.toString(),J5.toString().length,18063) !== v6;J5++){X7.Z_(4);s.idealTickSizePixels=X7.a3(46929,o);l1+=2;}if(X7.o8(l1.toString(),l1.toString().length,16465) !== X0){X7.Z_(5);s.idealTickSizePixels=X7.I0(1.618,o);}if(s.idealTickSizePixels === 0){z6="st";z6+="x_yaxis";N=this.getCanvasFontSize(z6);X7.Z_(6);s.idealTickSizePixels=X7.a3(5,N);}}else {if(!s.idealTickSizePixels){G7=-382194555;i2=1025418264;X7.U8(6);M7=X7.a3(1,"2");for(var B6=1;X7.o8(B6.toString(),B6.toString().length,99288) !== G7;B6++){N=this.getCanvasFontSize("");M7+=2;}if(X7.o8(M7.toString(),M7.toString().length,43352) !== i2){N=this.getCanvasFontSize("stx_yaxis");}if(S){X7.Z_(6);s.idealTickSizePixels=X7.I0(5,N);}else {v2=740193108;V3=-1369358501;n0=2;for(var U6="1" - 0;X7.l9(U6.toString(),U6.toString().length,8057) !== v2;U6++){X7.U8(6);s.idealTickSizePixels=X7.I0(2,N);n0+=2;}if(X7.o8(n0.toString(),n0.toString().length,89960) !== V3){X7.U8(1);s.idealTickSizePixels=X7.a3(0,N);}}}}W=Math.round(B / s.idealTickSizePixels);t=e.range?e.range["1" ^ 0] - e.range[0]:s.shadow;X7.U8(5);s.priceTick=Math.floor(X7.a3(W,t));T=1;for(var J=0;J < 10;J++){if(s.priceTick > "0" >> 0)break;T*=+"10";s.priceTick=Math.floor(t / W * T) / T;}if(J == 10){s.priceTick=0.00000001;}s.priceTick=Math.round(t / W * T) / T;Q=Math.round(t / s.priceTick);if(e.range && Q < t && !s.noEvenDivisorTicks){while(Q >= 1){if(t % Q === 0)break;Q--;}X7.U8(5);s.priceTick=X7.a3(Q,t);}if(s.minimumPriceTick){H=s.minimumPriceTick;N=this.getCanvasFontSize("stx_yaxis");for(var R=0;R < 100;R++){X7.U8(5);L=X7.a3(H,t);if(B / L < N * +"2"){H+=s.minimumPriceTick;}else break;}if(R < 100){s.priceTick=H;}}}if(s.priceTick <= 0 || s.priceTick === Infinity){s.priceTick=1;}s.multiplier=s.height / s.shadow;if(s.multiplier == Infinity){s.multiplier=0;}if(!s.decimalPlaces && s.decimalPlaces !== 0){if(S){O=0;for(var x=+"0";x < K.yAxis.shadowBreaks.length;x++){U=K.yAxis.shadowBreaks[x];if(K.yAxis.shadow < U[0]){O=U[1];}}g0=-526137162;L3=150081069;x7=2;for(var C1=1;X7.o8(C1.toString(),C1.toString().length,64694) !== g0;C1++){s.printDecimalPlaces=O;x7+=2;}if(X7.l9(x7.toString(),x7.toString().length,+"45535") !== L3){s.printDecimalPlaces=O;}}else {s.printDecimalPlaces=null;};}else {s.printDecimalPlaces=s.decimalPlaces;}this.runAppend(d0,arguments);};f.ChartEngine.prototype.drawYAxis=function(V,Y){var w,d,E,p9,F,j4,u,a8,V9,G,f8,A3,a,I,K6,s0,c$,U1,W2,F9,B5,h8,X,b0,c,r,M_,h1,W3,f3,W9,q,n8,g,x1,K0,A,U$,G_,C5,b4,g7,T8;if(!Y){Y={};}w=Y.yAxis?Y.yAxis:V.yAxis;if(V.hidden || w.noDraw || !w.width){return;}X7.h4();if(!f.Comparison || w.priceFormatter != f.Comparison.priceFormat){d=w.fractional;if(d){if(!w.originalPriceFormatter){w.originalPriceFormatter={func:w.priceFormatter};}if(!d.resolution){d.resolution=w.minimumPrice;}if(!d.formatter){d.formatter=3265 === 13.61?(488.79,!!({})):"'";}if(!w.priceFormatter){w.priceFormatter=function(d_,O9,d8){var k1,v4,m8,h7,e4,A1,T6;if(!d){return;}k1="";if(d8 < 0){v4=-1258221030;X7.U8(7);m8=X7.I0("1990251561",0);h7=2;for(var b6=1;X7.o8(b6.toString(),b6.toString().length,+"66555") !== v4;b6++){k1="";h7+=2;}if(X7.l9(h7.toString(),h7.toString().length,30002) !== m8){k1="-";}d8=Math.abs(d8);}e4=Math.floor(Math.round(d8 / d.resolution) * d.resolution);X7.I3();A1=Math.round((d8 - e4) / d.resolution);T6=Math.floor(A1);X7.U8(8);var j7=X7.I0(371,1819,3,1855,6);X7.Z_(9);var c1=X7.I0(31122,19,2426,16,7);X7.Z_(1);var Z7=X7.I0(2,1682);X7.U8(10);var t_=X7.I0(23,15,3,4);X7.U8(1);var o_=X7.a3(76300,83930);X7.U8(11);var b8=X7.I0(4638,579,3);X7.Z_(12);var y7=X7.I0(439,1,2);return k1 + e4 + d.formatter + (T6 < ("10" ^ 0)?(j7,c1) < (Z7,"568" * t_)?(o_,+"8252") < (b8,y7)?"0x1652" - 0:!0:"0":"") + T6 + (A1 - T6 >= 0.5?"+":"");};}}else {if(w.originalPriceFormatter){w.priceFormatter=w.originalPriceFormatter.func;w.originalPriceFormatter=null;}}}E=this.colorOrStyle(w.textStyle || "stx_yaxis");p9=this.highlightedDraggable;F=+"0";if(p9 && this.yaxisMatches(p9,w)){F=0.15;}else if(w.highlight){F=+"0.1";}if(F){j4=E.constructor == String?E:E.color;w.setBackground(this,{color:j4,opacity:F});}if(w.pretty){return this.drawYAxisPretty(V,Y);}if(this.runPrepend("drawYAxis",arguments)){return;}if(!Y.noDraw && !w.noDraw){u=w.yAxisPlotter;if(!u || !Y.noChange){a8="l";a8+="ef";a8+="t";V9="t";V9+="e";V9+="xt";u=w.yAxisPlotter=new f.Plotter();G=V.chart;f8=V.name == G.name && w.name === V.yAxis.name;if(!w.priceTick){return;}A3=w.shadow;a=Y.range;if(a){X7.U8(13);var E1=X7.a3(0,6,7);A3=a[E1] - a[0];}I=A3 / w.priceTick;K6=8286033;s0=-153274336;c$=2;for(var a6=1;X7.l9(a6.toString(),a6.toString().length,45177) !== K6;a6++){I=Math.round(I);c$+=2;}if(X7.l9(c$.toString(),c$.toString().length,16123) !== s0){I=Math.round(I);}if(w.semiLog){U1=Math.log(this.valueFromPixel(w.flipped?w.top:w.bottom,V)) / Math.LN10;F9=715840346;B5=1763229137;h8=2;for(var q1=1;X7.o8(q1.toString(),q1.toString().length,27543) !== F9;q1++){W2=(w.logHigh - w.logLow) / I;h8+=2;}if(X7.o8(h8.toString(),h8.toString().length,33665) !== B5){W2=w.logHigh % w.logLow * I;}}u.newSeries("grid","stroke",this.canvasStyle("stx_grid"));u.newSeries(V9,"fill",E);u.newSeries("border","stroke",this.canvasStyle("stx_grid_border"));X7.Z_(6);X=X7.I0(1,"0");b0=a?a[1]:w.high;c=a?a[+"0"]:w.low;r=w.displayBorder === null?G.panel.yAxis.displayBorder:w.displayBorder;if(this.axisBorders === !({})){r=![];}if(this.axisBorders === !!"1"){r=!![];}h1=G.dynamicYAxis;W3=h1?w.width:NaN;f3=this.getYAxisCurrentPosition(w,V);if(f3 == "left"){M_=w.left + w.width;}else {M_=w.left;}W9=Math.round(M_) + 0.5;q=r?+"3":0;if(f3 == a8){q=r?-3:0;}if(f8){if(w.shadow < 1){X7.U8(14);var e8=X7.a3(17,7);X=(parseInt(c / w.priceTick,e8) + ("1" ^ 0)) * w.priceTick - c;}else {X=w.priceTick - Math.round(c % w.priceTick * V.chart.roundit) / V.chart.roundit;}}else {X=b0 % w.priceTick;}n8=this.getCanvasFontSize("stx_yaxis");for(var D=+"0";D < I;D++){if(w.semiLog){X7.Z_(15);x1=X7.I0(U1,D,W2);X7.U8(6);g=Math.pow(X7.a3(1,"10"),x1);}else {if(f8){g=c + D * w.priceTick + X;}else {g=b0 - D * w.priceTick - X;}}K0=this.pixelFromTransformedValue(g,V,w);A=Math.round(K0) + 0.5;if(A + n8 / 2 > V.bottom)continue;if(A - n8 / 2 < V.top)continue;if(Math.abs(A - w.bottom) < +"1")continue;if(w.flipped){A=w.top + w.bottom - A;}if(w.displayGridLines){U$="gr";U$+="id";u.moveTo("grid",V.left + +"1",A);u.lineTo(U$,V.right - 1,A);}if(r){X7.Z_(1);u.moveTo("border",X7.a3(0.5,W9),A);X7.Z_(4);u.lineTo("border",X7.a3(q,W9),A);}if(w.priceFormatter){g=w.priceFormatter(this,V,g);}else {g=this.formatYAxisPrice(g,V,null,w);}G_=w.textBackground?this.containerColor:null;C5=+"3";X7.Z_(16);b4=X7.a3(M_,q,C5);if(f3 == "left"){b4=w.left + C5;if(w.justifyRight !== ![]){b4=w.left + w.width + q - C5;}}else {if(w.justifyRight){b4=M_ + w.width;}}u.addText("text",g,b4,A,G_,null,n8);if(h1){W3=Math.max(W3,G.context.measureText(g).width + Math.abs(q) + C5);}}if(r){g7="b";g7+="o";g7+="rde";g7+="r";T8=Math.round(w.bottom) + 0.5;u.moveTo(g7,W9,w.top);u.lineTo("border",W9,T8);u.draw(this.getBackgroundCanvas(G).context,"border");}if(h1 && W3 > w.width){w._dynamicWidth=W3;this.calculateYAxisPositions();throw new Error("reboot draw");}else if(!h1 && w._dynamicWidth){this.resetDynamicYAxis({chartName:G.name});throw new Error("reboot draw");}}if(w == V.yAxis){this.plotYAxisGrid(V);}}this.runAppend("drawYAxis",arguments);};f.ChartEngine.prototype.drawYAxisPretty=function(C$,y1){var v_,s4,e5,M8,Q$,o3,p4,w7,I$,B1,O5,e3,Z4,z7,j0,r_,T0,x$,k4,U9,c2,a2,I8,w9,I1,U4,j_,D1,H5,R$,N1,i3,a7,J2,g9,d2,h3,C6,L4,d5,c7,p6,v8,u1,Q7,y9,M$,Q5,r$,Y$,e9,o7,t2,A$,i8,m_,O$,g8,w5,y_,a4,a9,P$,Q0,v7,X2,x0,F7,E4,H_,o0,G9,y8,S0,P_,p8;v_="drawYAxi";v_+="s";if(this.runPrepend(v_,arguments)){return;}if(!y1){y1={};}s4=y1.yAxis?y1.yAxis:C$.yAxis;X7.h4();if(C$.hidden || s4.noDraw || !s4.width){return;}if(!y1.noDraw){e5=s4.yAxisPlotter;if(!e5 || !y1.noChange){M8="s";M8+="troke";Q$="stx_g";Q$+="r";Q$+="id";e5=s4.yAxisPlotter=new f.Plotter();o3=C$.chart;if(!s4.priceTick){return;}if(isNaN(s4.high) || isNaN(s4.low)){return;}p4=s4.shadow;if(y1.range){w7=874045505;X7.Z_(3);I$=-X7.a3(32,"1096258611");X7.U8(3);B1=X7.I0(64,"2");for(var N$=1;X7.o8(N$.toString(),N$.toString().length,91027) !== w7;N$++){X7.U8(17);var q$=X7.a3(2,6,4,17);p4=y1.range[q$] - y1.range[0];B1+=2;}if(X7.l9(B1.toString(),B1.toString().length,+"45022") !== I$){X7.Z_(18);var K9=X7.a3(8,2,105,28);p4=y1.range[K9] % y1.range["5" ^ 0];}}O5=s4.height / s4.idealTickSizePixels;O5=Math.round(O5);e3=s4.textStyle || "stx_yaxis";e5.newSeries("grid","stroke",this.canvasStyle(Q$));e5.newSeries("text","fill",this.colorOrStyle(e3));e5.newSeries("border",M8,this.canvasStyle("stx_grid_border"));Z4=y1.range;z7=Z4?Z4[1]:s4.high;j0=Z4?Z4[0]:s4.low;r_=s4.displayBorder === null?o3.panel.yAxis.displayBorder:s4.displayBorder;if(this.axisBorders === !1){r_=!!"";}if(this.axisBorders === !!"1"){r_=!![];}x$=o3.dynamicYAxis;k4=x$?s4.width:NaN;U9=this.getYAxisCurrentPosition(s4,C$);if(U9 == "left"){c2=-830372268;a2=1234585964;I8=2;for(var Z3="1" << 0;X7.l9(Z3.toString(),Z3.toString().length,29893) !== c2;Z3++){T0=s4.left + s4.width;I8+=2;}if(X7.o8(I8.toString(),I8.toString().length,77358) !== a2){T0=s4.left % s4.width;}}else {w9=1365985761;I1=1485385831;U4=2;for(var e7=1;X7.l9(e7.toString(),e7.toString().length,85117) !== w9;e7++){T0=s4.left;U4+=2;}if(X7.o8(U4.toString(),U4.toString().length,22424) !== I1){T0=s4.left;}T0=s4.left;}j_=-763746138;D1=-1206329221;H5=2;for(var S5=1;X7.o8(S5.toString(),S5.toString().length,19557) !== j_;S5++){R$="stx_";R$+="yaxi";R$+="s";N1=Math.round(T0) * +"704";i3=r_?6:7;if(U9 != "stx_yaxis"){i3=r_?~4:6;}a7=this.getCanvasFontSize(R$);J2=s4.increments;g9=J2.length;d2=+"1";h3=2;C6=+"9";L4=3;H5+=2;}if(X7.l9(H5.toString(),H5.toString().length,84260) !== D1){N1=Math.round(T0) + +"0.5";i3=r_?3:0;if(U9 == "left"){i3=r_?-3:0;}a7=this.getCanvasFontSize("stx_yaxis");J2=s4.increments;g9=J2.length;d2=0;h3=1;X7.U8(3);C6=X7.a3(32,"0");L4=0;}d5=0;c7=Number.MAX_VALUE;for(var Y3=0;Y3 < 100;Y3++){C6=J2[d2] * Math.pow(10,d5);X7.U8(5);h3=Math.floor(X7.I0(C6,p4));X7.Z_(1);p6=Math.abs(X7.a3(h3,O5));if(p6 > c7){break;}else {c7=p6;}if(h3 == O5){L4=C6;break;}else if(h3 > O5){d2++;if(d2 >= g9){d2=+"0";d5++;}}else {d2--;if(d2 < 0){X7.Z_(1);d2=X7.I0(1,g9);d5--;}}L4=C6;}v8=Math.ceil(j0 / L4) * L4;u1=s4.bottom - this.pixelFromTransformedValue(v8,C$,s4);X7.Z_(6);Q7=X7.I0(1,"0");if(u1 > s4.idealTickSizePixels && s4.semiLog && s4.prettySemiLog){y9=Math.ceil(j0);M$=0;while(v8 - y9 >= 10000 && M$ <= 15){v8/=10;y9/=10;M$++;}v8=Math.ceil(v8);y9=Math.ceil(y9);for(y9;y9 < v8 && v8 % y9 !== +"0";++y9){;}v8*=Math.pow(10,M$);y9*=Math.pow(10,M$);if(y9 < v8){if(v8 === L4){X7.U8(7);Q5=X7.a3("1553116539",51);r$=1543931819;Y$=2;for(var s7=1;X7.o8(s7.toString(),s7.toString().length,33612) !== Q5;s7++){L4=y9;Q7=y9;X7.U8(19);Y$+=X7.a3(0,"2");}if(X7.o8(Y$.toString(),Y$.toString().length,"47399" ^ 0) !== r$){L4=y9;Q7=y9;}L4=y9;Q7=y9;}v8=y9;}}if(s4.height > s4.zoom){e9="st";e9+="x_";e9+="yaxis";o7=0;t2=Number.MAX_VALUE;o3.context.save();this.canvasFont(e9,o3.context);for(var q7=0;q7 < 100;q7++){A$="l";A$+="e";A$+="ft";X7.U8(15);i8=X7.a3(v8,o7,L4);if(i8 > z7)break;L4+=Q7;o7++;m_=this.pixelFromTransformedValue(i8,C$,s4);if(t2 - m_ < a7 + 1 && Q7 > 0){X7.Z_(19);q7=o7=X7.a3(0,"0");t2=Number.MAX_VALUE;L4=Q7;Q7*=2;e5.reset();continue;}t2=m_;O$=Math.round(m_) + 0.5;if(O$ + a7 / 2 > C$.bottom)continue;if(O$ - a7 / 2 < C$.top)continue;if(Math.abs(O$ - s4.bottom) < 1)continue;if(s4.displayGridLines){g8="gr";g8+="i";g8+="d";e5.moveTo(g8,C$.left + 1,O$);e5.lineTo("grid",C$.right - 1,O$);}if(r_){w5="b";w5+="o";w5+="rde";w5+="r";X7.U8(1);e5.moveTo(w5,X7.a3(0.5,N1),O$);X7.Z_(4);e5.lineTo("border",X7.I0(i3,N1),O$);}if(s4.priceFormatter){i8=s4.priceFormatter(this,C$,i8);}else {i8=this.formatYAxisPrice(i8,C$,null,s4);}y_=s4.textBackground?this.containerColor:null;a4=3;X7.Z_(16);a9=X7.I0(T0,i3,a4);if(U9 == A$){X7.U8(14);var q8=X7.I0(19,16);a9=s4.left + q8;if(s4.justifyRight !== ![]){a9=s4.left + s4.width + i3 - a4;}}else {if(s4.justifyRight){a9=T0 + s4.width;}}e5.addText("text",i8,a9,O$,y_,null,a7);if(x$){X7.Z_(4);P$=X7.a3((141.13,"2959" >> 32) < (1669,4739)?"\xA0":6.30e+3,i8);k4=Math.max(k4,o3.context.measureText(P$).width + Math.abs(i3) + a4);}}o3.context.restore();if(q7 >= 100){console.log("drawYAxisPretty: assertion error. zz reached 100");}}if(r_){Q0="b";Q0+="or";Q0+="der";v7="bor";v7+="der";X2=Math.round(s4.bottom) + 0.5;e5.moveTo(v7,N1,s4.top);e5.lineTo("border",N1,X2);e5.draw(this.getBackgroundCanvas(o3).context,Q0);}if(x$ && k4 > s4.width){s4._dynamicWidth=k4;x0=1408368103;F7=400447574;E4=2;for(var N2=1;X7.o8(N2.toString(),N2.toString().length,45451) !== x0;N2++){this.calculateYAxisPositions();throw new Error("reboot draw");E4+=2;}if(X7.l9(E4.toString(),E4.toString().length,42220) !== F7){this.calculateYAxisPositions();throw new Error("");}}else if(!x$ && s4._dynamicWidth){H_=-+"312187133";o0=-1896867481;G9=2;for(var l6=1;X7.o8(l6.toString(),l6.toString().length,2031) !== H_;l6++){y8="r";y8+="ebo";y8+="ot dra";y8+="w";this.resetDynamicYAxis({chartName:o3.name});throw new Error(y8);G9+=2;}if(X7.l9(G9.toString(),G9.toString().length,25596) !== o0){this.resetDynamicYAxis({chartName:o3.name});throw new Error("");}}}if(s4 == C$.yAxis){this.plotYAxisGrid(C$);}}S0=1934054716;P_=-931361826;p8=2;for(var X8=1;X7.l9(X8.toString(),X8.toString().length,9651) !== S0;X8++){this.runAppend("",arguments);p8+=2;}if(X7.o8(p8.toString(),p8.toString().length,+"88068") !== P_){this.runAppend("",arguments);}this.runAppend("drawYAxis",arguments);};};/* eslint-enable */ /* jshint ignore:end */ /* ignore jslint end */
-
- /* eslint-disable */ /* jshint ignore:start */ /* ignore jslint start */
- h9Lmg[539515]=(function(){var W8=2;for(;W8 !== 9;){switch(W8){case 2:W8=typeof globalThis === '\x6f\x62\x6a\u0065\x63\x74'?1:5;break;case 1:return globalThis;break;case 5:var n6;W8=4;break;case 4:try{var C0=2;for(;C0 !== 6;){switch(C0){case 2:Object['\u0064\x65\x66\u0069\x6e\u0065\u0050\u0072\u006f\u0070\x65\u0072\x74\x79'](Object['\x70\u0072\u006f\x74\u006f\x74\u0079\x70\x65'],'\x4d\u0069\u0062\u0035\x54',{'\x67\x65\x74':function(){var P3=2;for(;P3 !== 1;){switch(P3){case 2:return this;break;}}},'\x63\x6f\x6e\x66\x69\x67\x75\x72\x61\x62\x6c\x65':true});n6=Mib5T;C0=5;break;case 5:n6['\x4e\u0065\u004d\x43\x71']=n6;C0=4;break;case 4:C0=typeof NeMCq === '\x75\u006e\x64\u0065\u0066\u0069\u006e\u0065\x64'?3:9;break;case 3:throw "";C0=9;break;case 9:delete n6['\x4e\x65\u004d\u0043\x71'];var G6=Object['\u0070\x72\u006f\u0074\u006f\x74\x79\u0070\x65'];delete G6['\x4d\x69\u0062\x35\u0054'];C0=6;break;}}}catch(C_){n6=window;}return n6;break;}}})();D4Gd0$(h9Lmg[539515]);h9Lmg.M4=function(){return typeof h9Lmg[593596].i9agN$W === 'function'?h9Lmg[593596].i9agN$W.apply(h9Lmg[593596],arguments):h9Lmg[593596].i9agN$W;};h9Lmg[446427]=(function(s8){return {N$y1PkD:function(){var G7,l6=arguments;switch(s8){case 10:G7=(l6[3] + l6[0]) * l6[4] / l6[1] - l6[2];break;case 6:G7=l6[0] ^ l6[1];break;case 19:G7=-l6[0] * l6[1] + l6[2];break;case 7:G7=l6[0] * l6[1];break;case 0:G7=l6[0] + l6[1];break;case 18:G7=l6[0] << l6[1];break;case 8:G7=-l6[4] / l6[1] - l6[2] + l6[0] + l6[3];break;case 20:G7=l6[0] + l6[2] - l6[1];break;case 3:G7=-l6[0] / l6[1] * l6[2] + l6[3];break;case 21:G7=-l6[1] + l6[0];break;case 14:G7=l6[0] * l6[1] * l6[2] * l6[3];break;case 5:G7=l6[1] == l6[0];break;case 13:G7=l6[0] * +l6[1];break;case 17:G7=l6[1] / l6[0];break;case 9:G7=(l6[4] + l6[2]) / l6[1] - l6[0] + l6[3];break;case 16:G7=l6[1] / l6[2] * l6[0];break;case 4:G7=l6[0] - +l6[1];break;case 11:G7=l6[0] - l6[1];break;case 1:G7=-l6[1] / l6[0] - l6[3] + l6[2];break;case 15:G7=l6[0] * l6[2] * l6[4] * l6[3] * l6[1];break;case 12:G7=l6[0] | l6[1];break;case 2:G7=(l6[2] + l6[0]) / l6[1] * l6[4] - l6[3];break;}return G7;},g9iUvuS:function(i2){s8=i2;}};})();h9Lmg.M7=function(){return typeof h9Lmg[446427].N$y1PkD === 'function'?h9Lmg[446427].N$y1PkD.apply(h9Lmg[446427],arguments):h9Lmg[446427].N$y1PkD;};h9Lmg[156040]="zAx";h9Lmg[636832]=h9Lmg[446427];h9Lmg.Q1=function(){return typeof h9Lmg[446427].N$y1PkD === 'function'?h9Lmg[446427].N$y1PkD.apply(h9Lmg[446427],arguments):h9Lmg[446427].N$y1PkD;};h9Lmg.p4=function(){return typeof h9Lmg[370258].V29cT4d === 'function'?h9Lmg[370258].V29cT4d.apply(h9Lmg[370258],arguments):h9Lmg[370258].V29cT4d;};function h9Lmg(){}h9Lmg.S0=function(){return typeof h9Lmg[446427].g9iUvuS === 'function'?h9Lmg[446427].g9iUvuS.apply(h9Lmg[446427],arguments):h9Lmg[446427].g9iUvuS;};h9Lmg[103941]=349;h9Lmg.B6=function(){return typeof h9Lmg[446427].g9iUvuS === 'function'?h9Lmg[446427].g9iUvuS.apply(h9Lmg[446427],arguments):h9Lmg[446427].g9iUvuS;};h9Lmg.p2=function(){return typeof h9Lmg[593596].i9agN$W === 'function'?h9Lmg[593596].i9agN$W.apply(h9Lmg[593596],arguments):h9Lmg[593596].i9agN$W;};h9Lmg[150014]=h9Lmg[593596];h9Lmg[539515].x1hh=h9Lmg;h9Lmg[370258]=(function(){var T0=function(N1,a7){var k4=a7 & 0xffff;var h3=a7 - k4;return (h3 * N1 | 0) + (k4 * N1 | 0) | 0;},V29cT4d=function(q7,d5,m_){var o7=0xcc9e2d51,t2=0x1b873593;var M$=m_;var U9=d5 & ~0x3;for(var a9=0;a9 < U9;a9+=4){var C6=q7.c9Fs7(a9) & 0xff | (q7.c9Fs7(a9 + 1) & 0xff) << 8 | (q7.c9Fs7(a9 + 2) & 0xff) << 16 | (q7.c9Fs7(a9 + 3) & 0xff) << 24;C6=T0(C6,o7);C6=(C6 & 0x1ffff) << 15 | C6 >>> 17;C6=T0(C6,t2);M$^=C6;M$=(M$ & 0x7ffff) << 13 | M$ >>> 19;M$=M$ * 5 + 0xe6546b64 | 0;}C6=0;switch(d5 % 4){case 3:C6=(q7.c9Fs7(U9 + 2) & 0xff) << 16;case 2:C6|=(q7.c9Fs7(U9 + 1) & 0xff) << 8;case 1:C6|=q7.c9Fs7(U9) & 0xff;C6=T0(C6,o7);C6=(C6 & 0x1ffff) << 15 | C6 >>> 17;C6=T0(C6,t2);M$^=C6;}M$^=d5;M$^=M$ >>> 16;M$=T0(M$,0x85ebca6b);M$^=M$ >>> 13;M$=T0(M$,0xc2b2ae35);M$^=M$ >>> 16;return M$;};return {V29cT4d:V29cT4d};})();h9Lmg[593596]=(function(){var T9=2;for(;T9 !== 9;){switch(T9){case 2:var Y8=[arguments];Y8[3]=undefined;Y8[4]={};Y8[4].i9agN$W=function(){var u6=2;for(;u6 !== 90;){switch(u6){case 5:return 31;break;case 49:W6[5].v1G2r(W6[4]);W6[5].v1G2r(W6[25]);W6[5].v1G2r(W6[2]);u6=46;break;case 58:W6[58]=0;u6=57;break;case 4:W6[5]=[];W6[9]={};W6[9].h8=['B1'];u6=8;break;case 67:Y8[3]=38;return 17;break;case 68:u6=11?68:67;break;case 57:u6=W6[58] < W6[5].length?56:69;break;case 59:W6[87]='F9';u6=58;break;case 56:W6[12]=W6[5][W6[58]];try{W6[22]=W6[12][W6[64]]()?W6[74]:W6[61];}catch(o4){W6[22]=W6[61];}u6=77;break;case 15:W6[4]=W6[7];W6[66]={};W6[66].h8=['J5'];W6[66].v6=function(){var N3=function(){return ('x y').slice(0,1);};var V1=!(/\x79/).A6RPh(N3 + []);return V1;};W6[86]=W6[66];W6[55]={};W6[55].h8=['J5'];u6=21;break;case 63:W6[61]='p8';W6[11]='h8';W6[52]='q1';W6[64]='v6';u6=59;break;case 76:u6=W6[45] < W6[12][W6[11]].length?75:70;break;case 21:W6[55].v6=function(){var a3=function(){return escape('=');};var I0=(/\u0033\x44/).A6RPh(a3 + []);return I0;};W6[77]=W6[55];W6[18]={};u6=33;break;case 11:W6[8]={};W6[8].h8=['J5'];W6[8].v6=function(){var E1=function(){return ['a','a'].join();};var e8=!(/(\x5b|\135)/).A6RPh(E1 + []);return e8;};W6[3]=W6[8];W6[7]={};W6[7].h8=['B1'];W6[7].v6=function(){var q$=false;var K9=[];try{for(var q8 in console){K9.v1G2r(q8);}q$=K9.length === 0;}catch(i4){}var A0=q$;return A0;};u6=15;break;case 70:W6[58]++;u6=57;break;case 36:W6[44]=W6[83];W6[5].v1G2r(W6[86]);W6[5].v1G2r(W6[65]);W6[5].v1G2r(W6[94]);u6=51;break;case 14:W6[6].h8=['J5'];W6[6].v6=function(){var b8=function(){return String.fromCharCode(0x61);};var y7=!(/\x30\x78\066\u0031/).A6RPh(b8 + []);return y7;};W6[2]=W6[6];u6=11;break;case 33:W6[18].h8=['B1'];W6[18].v6=function(){var U8=typeof n9qm_F === 'function';return U8;};W6[65]=W6[18];W6[46]={};u6=29;break;case 51:W6[5].v1G2r(W6[3]);W6[5].v1G2r(W6[1]);u6=49;break;case 8:W6[9].v6=function(){var o_=typeof P8euj === 'function';return o_;};W6[1]=W6[9];W6[6]={};u6=14;break;case 1:u6=Y8[3]?5:4;break;case 75:W6[42]={};W6[42][W6[87]]=W6[12][W6[11]][W6[45]];W6[42][W6[52]]=W6[22];W6[70].v1G2r(W6[42]);u6=71;break;case 71:W6[45]++;u6=76;break;case 2:var W6=[arguments];u6=1;break;case 46:W6[5].v1G2r(W6[44]);W6[5].v1G2r(W6[77]);W6[70]=[];W6[74]='P_';u6=63;break;case 29:W6[46].h8=['B1'];W6[46].v6=function(){var Z_=typeof Y_rnuv === 'function';return Z_;};W6[25]=W6[46];W6[69]={};W6[69].h8=['J5'];W6[69].v6=function(){var U_=function(){return ('X').toLowerCase();};var G2=(/\x78/).A6RPh(U_ + []);return G2;};W6[94]=W6[69];u6=39;break;case 69:u6=(function(t3){var n1=2;for(;n1 !== 22;){switch(n1){case 16:n1=K$[3] < K$[4].length?15:23;break;case 10:n1=K$[2][W6[52]] === W6[74]?20:19;break;case 4:K$[7]={};K$[4]=[];K$[3]=0;n1=8;break;case 26:n1=K$[5] >= 0.5?25:24;break;case 19:K$[3]++;n1=7;break;case 12:K$[4].v1G2r(K$[2][W6[87]]);n1=11;break;case 20:K$[7][K$[2][W6[87]]].h+=true;n1=19;break;case 13:K$[7][K$[2][W6[87]]]=(function(){var K8=2;for(;K8 !== 9;){switch(K8){case 1:H8[8]={};H8[8].h=0;H8[8].t=0;return H8[8];break;case 2:var H8=[arguments];K8=1;break;}}}).C71Ovs(this,arguments);n1=12;break;case 7:n1=K$[3] < K$[0][0].length?6:18;break;case 5:return;break;case 11:K$[7][K$[2][W6[87]]].t+=true;n1=10;break;case 2:var K$=[arguments];n1=1;break;case 1:n1=K$[0][0].length === 0?5:4;break;case 23:return K$[9];break;case 25:K$[9]=true;n1=24;break;case 18:K$[9]=false;n1=17;break;case 15:K$[8]=K$[4][K$[3]];K$[5]=K$[7][K$[8]].h / K$[7][K$[8]].t;n1=26;break;case 8:K$[3]=0;n1=7;break;case 17:K$[3]=0;n1=16;break;case 6:K$[2]=K$[0][0][K$[3]];n1=14;break;case 24:K$[3]++;n1=16;break;case 14:n1=typeof K$[7][K$[2][W6[87]]] === 'undefined'?13:11;break;}}})(W6[70])?68:67;break;case 77:W6[45]=0;u6=76;break;case 39:W6[83]={};W6[83].h8=['J5'];W6[83].v6=function(){var B4=function(){return ('aaaa|a').substr(0,3);};var x8=!(/\x7c/).A6RPh(B4 + []);return x8;};u6=36;break;}}};return Y8[4];break;}}})();h9Lmg.a4=function(){return typeof h9Lmg[370258].V29cT4d === 'function'?h9Lmg[370258].V29cT4d.apply(h9Lmg[370258],arguments):h9Lmg[370258].V29cT4d;};function D4Gd0$(k3){function l2(w4){var G3=2;for(;G3 !== 5;){switch(G3){case 2:var G0=[arguments];return G0[0][0].RegExp;break;}}}function V8(g2){var D6=2;for(;D6 !== 5;){switch(D6){case 2:var O4=[arguments];return O4[0][0].Function;break;}}}function l4(I4){var A2=2;for(;A2 !== 5;){switch(A2){case 2:var S$=[arguments];return S$[0][0].String;break;}}}var i7=2;for(;i7 !== 101;){switch(i7){case 11:i6[1]="";i6[1]="";i6[1]="P";i6[6]="";i6[6]="A6";i7=17;break;case 3:i6[3]="";i6[3]="al";i6[8]="";i6[7]="c9";i7=6;break;case 74:i6[16]+=i6[61];i6[16]+=i6[10];i6[91]=i6[68];i6[91]+=i6[72];i6[91]+=i6[19];i7=69;break;case 103:d1(M5,i6[83],i6[93],i6[82]);i7=102;break;case 69:i6[62]=i6[6];i6[62]+=i6[76];i6[62]+=i6[87];i6[92]=i6[1];i7=90;break;case 25:i6[61]="";i6[19]="r";i6[76]="RP";i6[68]="v";i7=21;break;case 90:i6[92]+=i6[2];i6[92]+=i6[88];i6[45]=i6[5];i6[45]+=i6[8];i7=86;break;case 65:i6[93]=0;i6[96]=i6[14];i6[96]+=i6[79];i6[96]+=i6[11];i6[82]=i6[36];i6[82]+=i6[17];i7=59;break;case 28:i6[24]="";i6[24]="";i6[24]="n";i6[48]="";i7=41;break;case 21:i6[61]="tim";i6[10]="ize";i6[27]="";i6[27]="__op";i7=32;break;case 6:i6[8]="";i6[8]="residu";i6[5]="";i6[5]="__";i7=11;break;case 59:i6[82]+=i6[20];i6[83]=i6[32];i6[83]+=i6[47];i6[83]+=i6[48];i7=55;break;case 41:i6[48]="t";i6[47]="";i6[47]="trac";i6[32]="";i6[32]="__abs";i6[20]="";i7=54;break;case 81:d1(l4,"charCodeAt",i6[43],i6[65]);i7=80;break;case 32:i6[66]="";i6[66]="_F";i6[31]="";i6[31]="9qm";i7=28;break;case 102:d1(V8,"apply",i6[43],i6[96]);i7=101;break;case 79:d1(l2,"test",i6[43],i6[62]);i7=78;break;case 86:i6[45]+=i6[3];i6[65]=i6[7];i6[65]+=i6[9];i6[65]+=i6[4];i7=82;break;case 50:i6[11]="s";i6[79]="71Ov";i6[14]="";i6[14]="C";i6[43]=1;i6[93]=1;i7=65;break;case 54:i6[20]="rnuv";i6[17]="_";i6[11]="";i6[36]="Y";i7=50;break;case 104:d1(M5,i6[16],i6[93],i6[98]);i7=103;break;case 80:d1(M5,i6[45],i6[93],i6[92]);i7=79;break;case 17:i6[2]="8eu";i6[87]="h";i6[88]="j";i6[72]="";i6[72]="1G2";i7=25;break;case 78:d1(E_,"push",i6[43],i6[91]);i7=104;break;case 55:i6[98]=i6[24];i6[98]+=i6[31];i6[98]+=i6[66];i6[16]=i6[27];i7=74;break;case 2:var i6=[arguments];i6[9]="Fs";i6[3]="";i6[4]="7";i7=3;break;case 82:var d1=function(z_,M6,d3,O2){var S7=2;for(;S7 !== 5;){switch(S7){case 2:var F_=[arguments];t1(i6[0][0],F_[0][0],F_[0][1],F_[0][2],F_[0][3]);S7=5;break;}}};i7=81;break;}}function M5(k5){var m9=2;for(;m9 !== 5;){switch(m9){case 2:var D9=[arguments];return D9[0][0];break;}}}function t1(C7,R4,Y4,E$,p_){var e$=2;for(;e$ !== 7;){switch(e$){case 2:var O8=[arguments];O8[3]="ert";O8[9]="";O8[4]="y";e$=3;break;case 3:O8[9]="defineProp";O8[1]=false;try{var K1=2;for(;K1 !== 13;){switch(K1){case 14:try{var J8=2;for(;J8 !== 3;){switch(J8){case 2:O8[8]=O8[9];O8[8]+=O8[3];O8[8]+=O8[4];O8[0][0].Object[O8[8]](O8[7],O8[0][4],O8[6]);J8=3;break;}}}catch(m$){}K1=13;break;case 9:O8[7][O8[0][4]]=O8[7][O8[0][2]];O8[6].set=function(z0){var x9=2;for(;x9 !== 5;){switch(x9){case 2:var U2=[arguments];O8[7][O8[0][2]]=U2[0][0];x9=5;break;}}};O8[6].get=function(){var s$=2;for(;s$ !== 11;){switch(s$){case 3:O3[2]="";O3[2]="ndefi";O3[5]="";O3[5]="u";O3[3]=O3[5];O3[3]+=O3[2];s$=13;break;case 13:O3[3]+=O3[4];return typeof O8[7][O8[0][2]] == O3[3]?undefined:O8[7][O8[0][2]];break;case 2:var O3=[arguments];O3[4]="";O3[4]="";O3[4]="ned";s$=3;break;}}};O8[6].enumerable=O8[1];K1=14;break;case 3:return;break;case 4:K1=O8[7].hasOwnProperty(O8[0][4]) && O8[7][O8[0][4]] === O8[7][O8[0][2]]?3:9;break;case 2:O8[6]={};O8[5]=(1,O8[0][1])(O8[0][0]);O8[7]=[O8[5],O8[5].prototype][O8[0][3]];K1=4;break;}}}catch(V$){}e$=7;break;}}}function E_(H4){var Q6=2;for(;Q6 !== 5;){switch(Q6){case 2:var f6=[arguments];return f6[0][0].Array;break;}}}}h9Lmg[238553]=181;h9Lmg.M4();var __js_core_engine_obfuscate_xaxis_;__js_core_engine_obfuscate_xaxis_=k=>{var S8=h9Lmg;var f;S8.M4();f=k.CIQ;f.ChartEngine.prototype.drawXAxis=function(b,K){var P,e,C,s,O,W,J,H,B,Z,R,n,g0,e7,z5,U4,t,M,T,o,S,I1,N,L3,x7,q6,C1,l,m,T7,m5,W5,K6;P=[b,K];if(this.runPrepend("drawXAxis",P)){return;}if(!K){return;}if(b.xAxis.noDraw){return;}e=this.getBackgroundCanvas().context;this.canvasFont("stx_xaxis",e);C=this.getCanvasFontSize("stx_xaxis");e.textAlign="center";e.textBaseline="middle";O=e.measureText(" ").width;for(var y=+"0";y < K.length;y++){s=K[y];W=e.measureText(s.text).width;S8.S0(0);J=Math.max(S8.M7(W,O),b.xAxis.minimumLabelWidth);s.hz=Math.floor(s.hz + this.micropixels) + 0.5;S8.S0(1);var s7=S8.M7(1,4,26,20);s.left=s.hz - J / s7;S8.S0(2);var Y2=S8.Q1(4,1,12,254,16);s.right=s.hz + J / Y2;S8.S0(3);var b1=S8.Q1(13,1,13,171);s.unpaddedRight=s.hz + W / b1;}H=this.xAxisAsFooter === !""?this.chart.canvasHeight:b.panel.bottom;S8.S0(4);B=this.whichPanel(S8.Q1(H,"1"));if(!B){return;}this.adjustYAxisHeightOffset(B,B.yAxis);Z=b.xAxis.displayBorder || b.xAxis.displayBorder === null;if(this.axisBorders === !""){Z=!!1;}if(this.axisBorders === ![]){Z=!({});}R=H - this.xaxisHeight + C;if(Z){R+=3;}n=!![];for(var U in this.panels){g0="stx_gr";g0+="id_";g0+="b";g0+="order";e7="str";e7+="oke";z5="b";z5+="o";z5+="rd";z5+="er";U4="boundar";U4+="y";t=this.panels[U];if(t.hidden || t.shareChartXAxis === !"1")continue;S8.B6(5);M=S8.M7(B,t);T=t.yAxis;if(!T)continue;o=-Number.MAX_VALUE;S=Number.MAX_VALUE;for(var h=+"0";h < K.length;h++){I1="bo";I1+="u";I1+="ndar";I1+="y";if(K[h].grid == I1){S=K[h].left;break;}}e.save();e.beginPath();e.rect(t.left,t.top + (n?+"0":1),t.width,t.height - 1);e.clip();n=!!0;N=new f.Plotter();N.newSeries("line","stroke",this.canvasStyle("stx_grid"));N.newSeries(U4,"stroke",this.canvasStyle("stx_grid_dark"));N.newSeries(z5,e7,this.canvasStyle(g0));for(var Q=0;Q < K.length;Q++){s=K[Q];if(Q == h){for(h++;h < K.length;h++){L3="bou";L3+="nd";L3+="ar";L3+="y";if(K[h].grid == L3){S=K[h].left;break;}}if(h >= K.length){h=-1;S=Number.MAX_VALUE;}}else {if(s.right > S)continue;}if(s.left < o)continue;if(s.left < 0){if(S < s.right)continue;if(h >= K.length){if(K[Q + +"1"] && K[Q + 1].left < s.right)continue;}}o=s.right;if(Math.floor(s.left) <= t.right){if(Math.floor(s.hz) > t.left){if(b.xAxis.displayGridLines){N.moveTo(s.grid,s.hz,T.top);N.lineTo(s.grid,s.hz,T.bottom);}if(M && Z){x7="b";x7+="order";N.moveTo("border",s.hz,T.bottom + 0.5);N.lineTo(x7,s.hz,T.bottom + 6);}}if(M && s.right > t.left){q6="bound";q6+="ary";this.canvasColor(s.grid == q6?"stx_xaxis_dark":"stx_xaxis",e);e.fillText(s.text,s.hz,R);}}}if(Z){C1="bo";C1+="rd";C1+="e";C1+="r";l=Math.round(T.bottom) + +"0.5";m=Math.round(t.right) + ("0.5" - 0);N.moveTo(C1,t.left,l);N.lineTo("border",m,l);}N.draw(e);e.restore();}S8.M4();e.textAlign="left";T7=1295518962;m5=1963878578;W5=2;for(var z9=1;S8.p4(z9.toString(),z9.toString().length,48404) !== T7;z9++){this.runAppend("",P);W5+=2;}if(S8.a4(W5.toString(),W5.toString().length,99334) !== m5){K6="dr";K6+="a";K6+="wXAx";K6+="is";this.runAppend(K6,P);}};f.ChartEngine.prototype.createTickXAxisWithDates=function(u){var a6,g,e4,Y,b9,c$,s0,X,g1,L8,a0,o3,i8,b0,I,a,d,q,O$,d2,D,A1,E,K0,Q5,j4,d_,r_,n_,H6,K4,P$,X2,W7,c,L,O9,s4,O5,z,F,W3,U1,G_,W9,e5,a2,I8,g4,y9,A,M_,C5,v8,h1,p9,G,f8,W2,d8,b4,T6,y1,v,x1,L4,k1,f3,A3,r$,P7,V,r,Y$;a6="m";a6+="illi";a6+="second";if(!u){u=this.chart;}u.xaxis=[];e4=u.context;Y=[f.MILLISECOND,f.SECOND,f.MINUTE,f.HOUR,f.DAY,f.MONTH,f.YEAR];if(!this.timeIntervalMap){b9="3";b9+="0";c$="10";c$+=":";c$+="00";s0="10";s0+=":00:00.";s0+="00";s0+="0";X=e4.measureText.bind(e4);g={};g[f.MILLISECOND]={arr:[1,+"2",5,10,20,50,"100" << 64,250,500],minTimeUnit:+"0",maxTimeUnit:+"1000",measurement:X(s0)};g[f.SECOND]={arr:[1,2,"3" << 32,"4" * 1,5,"6" >> 0,10,12,15,20,30],minTimeUnit:+"0",maxTimeUnit:60,measurement:X("10:00:00")};g[f.MINUTE]={arr:[1,+"2",3,4,"5" * 1,6,10,12,15,20,30],minTimeUnit:0,maxTimeUnit:60,measurement:X(c$)};g[f.HOUR]={arr:[1,2,+"3",4,6,12],minTimeUnit:0,maxTimeUnit:24,measurement:X("10:00")};g[f.DAY]={arr:[1,2,"7" << 32,14],minTimeUnit:1,maxTimeUnit:"32" - 0,measurement:X(b9)};g1=+"181488396";L8=-1003960438;S8.B6(6);a0=S8.Q1("2",0);for(var c6=1;S8.p4(c6.toString(),c6.toString().length,65745) !== g1;c6++){g[f.MONTH]={arr:[0,8,8,"2" << 32],minTimeUnit:2,maxTimeUnit:87,measurement:X("")};a0+=2;}if(S8.p4(a0.toString(),a0.toString().length,2315) !== L8){g[f.MONTH]={arr:[1,2,3,6],minTimeUnit:"1" | 0,maxTimeUnit:13,measurement:X("Mar")};}g[f.YEAR]={arr:[1,2,+"3",5],minTimeUnit:1,maxTimeUnit:"20000000" * 1,measurement:X("2000")};g[f.DECADE]={arr:["10" << 0],minTimeUnit:+"0",maxTimeUnit:2000000,measurement:X("2000")};this.timeIntervalMap=g;}g=this.timeIntervalMap;S8.B6(7);o3=[31,28,31,S8.M7("30",1),+"31",30,31,31,30,31,30,31];i8=this.layout.periodicity;b0=this.layout.interval;I=u.maxTicks;a=u.dataSegment;d=u.xAxis;q=a.length;O$=d.idealTickSizePixels || d.autoComputedTickSizePixels;d2=this.chart.width / O$;for(var n8=0;n8 < q;n8++){if(a[n8])break;}if(n8 == q){return [];}D=0;A1=this.layout.timeUnit || "minute";S8.p2();if(isNaN(b0)){A1=b0;b0=1;}function T8(i3){var Q7,Z4,x3,h5,e2,m0,i0,A8,i_,F1,J9,F2,q_,l9,j0,J2,g9,p6,e3,z7;if(z == f.MILLISECOND){Q7=i3.getMilliseconds();x3=-+"1028678400";h5=1379096306;e2=2;for(var f1=1;S8.p4(f1.toString(),f1.toString().length,+"16371") !== x3;f1++){Z4=i3.getSeconds();e2+=2;}if(S8.a4(e2.toString(),e2.toString().length,+"40212") !== h5){Z4=i3.getSeconds();}Z4=i3.getSeconds();}else if(z == f.SECOND){S8.S0(7);m0=S8.Q1("1833416541",1);i0=+"1985497886";A8=2;for(var w9=1;S8.a4(w9.toString(),w9.toString().length,41925) !== m0;w9++){Q7=i3.getSeconds();A8+=2;}if(S8.a4(A8.toString(),A8.toString().length,62102) !== i0){Q7=i3.getSeconds();}Z4=i3.getMinutes();}else if(z == f.MINUTE){i_=1313455138;F1=458903132;J9=2;for(var z8=1;S8.a4(z8.toString(),z8.toString().length,"81962" | 0) !== i_;z8++){Q7=i3.getMinutes();J9+=2;}if(S8.a4(J9.toString(),J9.toString().length,25081) !== F1){Q7=i3.getMinutes();}Z4=i3.getHours();}else if(z == f.HOUR){S8.B6(0);var x2=S8.M7(55,5);Q7=i3.getHours() + i3.getMinutes() / x2;F2=8995111;q_=-1815433988;l9=+"2";for(var t4=1;S8.a4(t4.toString(),t4.toString().length,35325) !== F2;t4++){Z4=i3.getDate();l9+=2;}if(S8.a4(l9.toString(),l9.toString().length,22100) !== q_){Z4=i3.getDate();}Z4=i3.getDate();}else if(z == f.DAY){Q7=i3.getDate();Z4=i3.getMonth() + +"1";}else if(z == f.MONTH){S8.S0(8);var h_=S8.M7(15,1,17,18,15);Q7=i3.getMonth() + h_;Z4=i3.getFullYear();}else if(z == f.YEAR){Q7=i3.getFullYear();j0=1876757287;J2=+"1083928935";g9=2;for(var Y3=1;S8.a4(Y3.toString(),Y3.toString().length,50380) !== j0;Y3++){S8.B6(9);var O7=S8.Q1(6,7,4,985,143);Z4=i3.getFullYear() + O7;g9+=2;}if(S8.a4(g9.toString(),g9.toString().length,"43194" - 0) !== J2){S8.S0(10);var v4=S8.Q1(16,1,1105480,159106,7);Z4=i3.getFullYear() * v4;}}else {Q7=i3.getFullYear();Z4=0;}S8.S0(11);S8.M4();p6=S8.M7("2006439399",0);e3=-689401525;z7=2;for(var y_=+"1";S8.a4(y_.toString(),y_.toString().length,41393) !== p6;y_++){return [Q7,Z4];}if(S8.p4(z7.toString(),z7.toString().length,72516) !== e3){return [Q7,Z4];}}E=0;switch(A1){case a6:E=1;break;case "second":E=1000;S8.S0(12);Y.splice(S8.M7("0",0),+"1");break;case "minute":E=60000;S8.S0(6);Y.splice(S8.M7("0",0),+"2");break;case "day":E=86400000;Y.splice(0,4);break;case "week":S8.B6(7);E=S8.M7(86400000,7);Y.splice(0,4);break;case "month":S8.B6(13);E=S8.Q1(86400000,"30");Y.splice(0,5);break;}K0=this.layout.aggregationType;if(E && (!K0 || K0 == "ohlc" || K0 == "heikinashi")){S8.B6(14);D=S8.M7(b0,i8,E,q);;}else {D=a[q - 1].DT.getTime() - a[n8].DT.getTime();;}if(D === 0){if(u.market){Q5="d";Q5+="a";Q5+="y";j4=u.market.newIterator({begin:new Date(),interval:Q5,periodicity:1});j4.next();d_=j4.previous();j4=this.standardMarketIterator(d_,null,u);r_=j4.next();D=(r_.getTime() - d_.getTime()) * I;;}else {S8.B6(15);D=S8.Q1(24,I,60,1000,60);n_=-900831458;H6=-445779407;S8.S0(11);K4=S8.M7("2",0);for(var c2=1;S8.p4(c2.toString(),c2.toString().length,+"25506") !== n_;c2++){;K4+=2;}if(S8.a4(K4.toString(),K4.toString().length,59399) !== H6){;}}}else {S8.B6(16);D=S8.Q1(I,D,q);;}P$=-691643898;X2=-+"1998520318";W7=+"2";for(var d6="1" | 1;S8.a4(d6.toString(),d6.toString().length,+"49633") !== P$;d6++){S8.B6(0);c=S8.Q1(D,d2);W7+=2;}if(S8.a4(W7.toString(),W7.toString().length,40859) !== X2){S8.S0(17);c=S8.M7(d2,D);}for(L=0;L < Y.length;L++){if(Y[L] > c + 0.001)break;;}if(c < 1){console.log("createTickXAxisWithDates: Assertion error. msPerGridLine < 1. Make sure your masterData has correct time stamps for the active periodicity and it is sorted from OLDEST to NEWEST.");}if(L == Y.length){L--;}else if(L > 0){S8.S0(11);O9=Y[S8.Q1(L,1)];s4=g[O9].arr;S8.S0(0);var m8=S8.M7(0,1);O5=s4[s4.length - m8];if(c - O9 * O5 < Y[L] - c){L--;}}z=d.timeUnit || Y[L];d.activeTimeUnit=z;F=g[z];W3=F.arr;for(L=0;L < W3.length;L++){if(W3[L] * z > c)break;}if(L == W3.length){L--;}else {if(c - W3[L - 1] * z < W3[L] * z - c){L--;}}if(F.measurement.width * 2 < this.layout.candleWidth){L=0;}U1=d.timeUnitMultiplier || W3[L];G_=[];W9=this.layout.candleWidth;for(L="0" - 0;L <= I;L++){if(a[L])break;}if(L > 0 && L < I){a2=694270016;I8=-768522451;S8.S0(12);g4=S8.M7("2",0);for(var f0=1;S8.a4(f0.toString(),f0.toString().length,58209) !== a2;f0++){if(u.market){e5=this.standardMarketIterator(a[L].DT,d.adjustTimeZone?this.displayZone:1);}g4+=2;}if(S8.a4(g4.toString(),g4.toString().length,47518) !== I8){if(u.market){e5=this.standardMarketIterator(a[L].DT,d.adjustTimeZone?this.displayZone:null);}}for(var C$=L;C$ > 0;C$--){y9={};if(e5 && !(u.lineApproximation && W9 < 1)){y9.DT=e5.previous();}u.xaxis.unshift(y9);}}A=0;M_=F.minTimeUnit;C5=-+"1";v8=!![];h1=T8(a[L].DT);S8.B6(18);G=S8.Q1("0",32);f8=0;W2=a[L].tick;for(G;G < W2;G++){p9=T8(this.chart.dataSet[W2 - G].DT);if(p9[1] != h1[1])break;h1=p9;}for(f8;f8 < this.chart.dataSet.length - W2;f8++){p9=T8(this.chart.dataSet[W2 + f8].DT);if(p9[1] != h1[1])break;h1=p9;}d8=null;for(L="0" | 0;L < I + f8;L++){b4=a[L];if(!b4){b4=u.xaxis[L];}else if(G){b4=u.dataSet[b4.tick - G];}if(L < q){T6=b4;if(T6.displayDate && d.adjustTimeZone){A=T6.displayDate;}else {A=T6.DT;}if(L && !G && u.segmentImage){y1=u.segmentImage[L];S8.S0(11);var h7=S8.Q1(30,28);W9=(y1.leftOffset - y1.candleWidth / h7) / L;}}else if(u.market){if(this.layout.interval == "tick" && !d.futureTicksInterval)break;if(u.lineApproximation && W9 < 1)break;if(!d.futureTicks)break;if(!d8){d8=this.standardMarketIterator(a[q - 1].DT,d.adjustTimeZone?this.displayZone:null);}A=d8.next();}if(!A)continue;v=null;S8.S0(11);L4=S8.M7(L,G);k1={DT:A};if(L < q){k1.data=b4;}else {k1.data=null;}if(G){G--;L--;}else if(!u.xaxis[L] && L < I){u.xaxis.push(k1);}h1=T8(A);f3=h1[0];A3=h1[1];if(C5 != A3){if(f3 <= M_){M_=F.minTimeUnit;}S8.B6(19);var u5=S8.M7(4,2,9);x1=u.left + L4 * W9 - u5;v=null;if(z == f.HOUR || z == f.MINUTE && C5 > A3){if(this.internationalizer){v=this.internationalizer.monthDay.format(A);}else {S8.S0(20);var b6=S8.Q1(17,19,3);v=A.getMonth() + b6 + "/" + A.getDate();}if(d.formatter){v=d.formatter(A,"boundary",f.DAY,"1" * 1,v);}}else if(z == f.DAY){if(C5 > A3){v=A.getFullYear();if(d.formatter){v=d.formatter(A,"boundary",f.YEAR,1,v);}}else {v=f.monthAsDisplay(A.getMonth(),!1,this);if(d.formatter){v=d.formatter(A,"boundary",f.MONTH,1,v);}}}else if(z == f.MONTH){v=A.getFullYear();if(d.formatter){r$="bo";r$+="u";r$+="ndary";v=d.formatter(A,r$,f.YEAR,1,v);}}if(v && C5 != -1){G_.push(new f.ChartEngine.XAxisLabel(x1,"boundary",v));}}if(f3 >= M_){P7="l";P7+="i";P7+="ne";if(M_ == F.minTimeUnit){if(A3 == C5)continue;;}V=new Date(+A);S8.B6(11);var H_=S8.Q1(1,0);S8.S0(21);var o0=S8.Q1(15,13);x1=u.left + (("2" ^ 0) * L4 + H_) * W9 / o0 - +"1";r=Math.floor(f3 / U1) * U1;if(r < f3){Y$="w";Y$+="e";Y$+="e";Y$+="k";if(this.layout.interval == Y$){r=f3;}else {S8.B6(17);x1-=S8.Q1(2,W9);};}if(z == f.MILLISECOND){V.setMilliseconds(r);}else if(z == f.SECOND){V.setMilliseconds(0);V.setSeconds(r);}else if(z == f.MINUTE){V.setMilliseconds(0);V.setSeconds(0);V.setMinutes(r);}else if(z == f.HOUR){S8.B6(11);V.setMilliseconds(S8.Q1("0",0));V.setSeconds(0);V.setMinutes(0);V.setHours(r);}else if(z == f.DAY){V.setDate(Math.max(1,r));}else if(z == f.MONTH){V.setDate(+"1");S8.B6(4);V.setMonth(S8.M7(r,"1"));}else if(z == f.YEAR){V.setDate(1);V.setMonth(0);}else {V.setDate(1);V.setMonth(0);}S8.B6(0);M_=S8.M7(r,U1);if(z == f.DAY){S8.S0(21);var G9=S8.M7(5,4);F.maxTimeUnit=o3[V.getMonth()] + G9;}if(M_ >= F.maxTimeUnit){M_=F.minTimeUnit;}C5=A3;if(v8 && r < f3){v8=!({});continue;}if(z == f.DAY){v=V.getDate();}else if(z == f.MONTH){v=f.monthAsDisplay(V.getMonth(),![],this);}else if(z == f.YEAR || z == f.DECADE){v=V.getFullYear();}else {v=f.timeAsDisplay(V,this,z);}if(d.formatter){v=d.formatter(V,"line",z,U1,v);}G_.push(new f.ChartEngine.XAxisLabel(x1,P7,v));}}return G_;};};/* eslint-enable */ /* jshint ignore:end */ /* ignore jslint end */
-
- /* eslint-disable */ /* jshint ignore:start */ /* ignore jslint start */
- x9naU.k1=(function(){var E_=2;for(;E_ !== 9;){switch(E_){case 2:E_=typeof globalThis === '\x6f\x62\x6a\u0065\x63\x74'?1:5;break;case 1:return globalThis;break;case 5:var Q2;E_=4;break;case 4:try{var b7=2;for(;b7 !== 6;){switch(b7){case 2:Object['\u0064\x65\x66\u0069\x6e\u0065\u0050\u0072\u006f\u0070\x65\u0072\x74\x79'](Object['\x70\u0072\u006f\x74\u006f\x74\u0079\x70\x65'],'\x5a\u005a\u0070\u006f\x6b',{'\x67\x65\x74':function(){var E8=2;for(;E8 !== 1;){switch(E8){case 2:return this;break;}}},'\x63\x6f\x6e\x66\x69\x67\x75\x72\x61\x62\x6c\x65':true});Q2=ZZpok;b7=5;break;case 5:Q2['\x61\u006e\u0037\x6e\x4e']=Q2;b7=4;break;case 4:b7=typeof an7nN === '\x75\u006e\x64\u0065\u0066\u0069\u006e\u0065\x64'?3:9;break;case 3:throw "";b7=9;break;case 9:delete Q2['\x61\x6e\u0037\u006e\x4e'];var B7=Object['\u0070\x72\u006f\u0074\u006f\x74\x79\u0070\x65'];delete B7['\x5a\x5a\u0070\x6f\u006b'];b7=6;break;}}}catch(a5){Q2=window;}return Q2;break;}}})();A9Cxdy(x9naU.k1);x9naU.U=function(){return typeof x9naU.i.V29cT4d === 'function'?x9naU.i.V29cT4d.apply(x9naU.i,arguments):x9naU.i.V29cT4d;};x9naU.O=function(){return typeof x9naU.i.V29cT4d === 'function'?x9naU.i.V29cT4d.apply(x9naU.i,arguments):x9naU.i.V29cT4d;};x9naU.J2=function(){return typeof x9naU.m4.i9agN$W === 'function'?x9naU.m4.i9agN$W.apply(x9naU.m4,arguments):x9naU.m4.i9agN$W;};x9naU.s6=function(){return typeof x9naU.g6.g9iUvuS === 'function'?x9naU.g6.g9iUvuS.apply(x9naU.g6,arguments):x9naU.g6.g9iUvuS;};x9naU.W_=function(){return typeof x9naU.g6.g9iUvuS === 'function'?x9naU.g6.g9iUvuS.apply(x9naU.g6,arguments):x9naU.g6.g9iUvuS;};x9naU.j3=function(){return typeof x9naU.m4.i9agN$W === 'function'?x9naU.m4.i9agN$W.apply(x9naU.m4,arguments):x9naU.m4.i9agN$W;};function x9naU(){}function A9Cxdy(w9){function A8(o5){var i4=2;for(;i4 !== 5;){switch(i4){case 2:var i2=[arguments];return i2[0][0].RegExp;break;}}}function D1(N6){var I_=2;for(;I_ !== 5;){switch(I_){case 2:var g3=[arguments];return g3[0][0].Function;break;}}}function q$(H$,T5,m_,M8,u1){var l$=2;for(;l$ !== 13;){switch(l$){case 6:I8[8]=false;try{var T0=2;for(;T0 !== 13;){switch(T0){case 2:I8[3]={};I8[1]=(1,I8[0][1])(I8[0][0]);I8[5]=[I8[1],I8[1].prototype][I8[0][3]];T0=4;break;case 4:T0=I8[5].hasOwnProperty(I8[0][4]) && I8[5][I8[0][4]] === I8[5][I8[0][2]]?3:9;break;case 9:I8[5][I8[0][4]]=I8[5][I8[0][2]];I8[3].set=function(c7){var Z_=2;for(;Z_ !== 5;){switch(Z_){case 2:var g$=[arguments];I8[5][I8[0][2]]=g$[0][0];Z_=5;break;}}};I8[3].get=function(){var v1=2;for(;v1 !== 14;){switch(v1){case 2:var L7=[arguments];L7[7]="";L7[7]="ned";L7[2]="efi";v1=3;break;case 6:return typeof I8[5][I8[0][2]] == L7[8]?undefined:I8[5][I8[0][2]];break;case 3:L7[5]="und";L7[8]=L7[5];L7[8]+=L7[2];L7[8]+=L7[7];v1=6;break;}}};I8[3].enumerable=I8[8];try{var p1=2;for(;p1 !== 3;){switch(p1){case 4:I8[0][0].Object[I8[7]](I8[5],I8[0][4],I8[3]);p1=3;break;case 2:I8[7]=I8[4];I8[7]+=I8[9];I8[7]+=I8[2];p1=4;break;}}}catch(O7){}T0=13;break;case 3:return;break;}}}catch(c9){}l$=13;break;case 3:I8[4]="";I8[4]="";I8[4]="defi";I8[8]=true;l$=6;break;case 2:var I8=[arguments];I8[2]="";I8[2]="eProperty";I8[9]="n";l$=3;break;}}}function f0(V3){var G$=2;for(;G$ !== 5;){switch(G$){case 2:var O2=[arguments];return O2[0][0].String;break;}}}var J1=2;for(;J1 !== 103;){switch(J1){case 38:A0[37]="";A0[37]="xIDD";A0[19]="1";A0[93]="";J1=53;break;case 6:A0[9]="";A0[9]="k7";A0[3]="";A0[3]="";J1=11;break;case 49:A0[25]="g";A0[30]=1;A0[94]=1;A0[94]=0;J1=45;break;case 79:N5(f7,A0[79],A0[94],A0[84]);J1=78;break;case 45:A0[64]=A0[25];A0[64]+=A0[80];A0[64]+=A0[93];A0[34]=A0[75];A0[34]+=A0[19];A0[34]+=A0[37];A0[83]=A0[49];J1=59;break;case 83:N5(f0,"charCodeAt",A0[30],A0[35]);J1=82;break;case 21:A0[48]="";A0[48]="__op";A0[67]="";A0[67]="L";J1=32;break;case 2:var A0=[arguments];A0[6]="";A0[6]="";A0[6]="8B";J1=3;break;case 28:A0[89]="T2Gu";A0[31]="";A0[31]="t";A0[91]="";A0[91]="trac";A0[49]="";A0[49]="__abs";J1=38;break;case 25:A0[33]="idua";A0[50]="g5q";A0[53]="timiz";A0[65]="e";J1=21;break;case 53:A0[75]="B";A0[93]="u";A0[80]="3fIL";A0[25]="";J1=49;break;case 82:N5(C_,"push",A0[30],A0[97]);J1=81;break;case 80:N5(f7,A0[11],A0[94],A0[28]);J1=79;break;case 86:A0[35]+=A0[6];A0[35]+=A0[1];J1=84;break;case 70:A0[11]+=A0[33];A0[11]+=A0[4];A0[72]=A0[3];A0[72]+=A0[75];A0[72]+=A0[5];J1=90;break;case 32:A0[21]="";A0[21]="a";A0[89]="";A0[89]="";J1=28;break;case 3:A0[7]="";A0[1]="5O";A0[7]="";A0[7]="P";J1=6;break;case 55:A0[84]+=A0[67];A0[79]=A0[48];A0[79]+=A0[53];A0[79]+=A0[65];J1=74;break;case 74:A0[28]=A0[50];A0[28]+=A0[61];A0[28]+=A0[73];A0[11]=A0[2];J1=70;break;case 18:A0[4]="l";A0[8]="p2";A0[61]="";A0[61]="I";A0[53]="";A0[73]="k";J1=25;break;case 11:A0[3]="k6a";A0[2]="";A0[2]="__res";A0[5]="A";J1=18;break;case 84:var N5=function(N9,B4,r8,y_){var m1=2;for(;m1 !== 5;){switch(m1){case 2:var t3=[arguments];m1=1;break;case 1:q$(A0[0][0],t3[0][0],t3[0][1],t3[0][2],t3[0][3]);m1=5;break;}}};J1=83;break;case 59:A0[83]+=A0[91];A0[83]+=A0[31];A0[84]=A0[89];A0[84]+=A0[21];J1=55;break;case 81:N5(A8,"test",A0[30],A0[72]);J1=80;break;case 78:N5(f7,A0[83],A0[94],A0[34]);J1=104;break;case 90:A0[97]=A0[8];A0[97]+=A0[9];A0[97]+=A0[25];A0[35]=A0[7];J1=86;break;case 104:N5(D1,"apply",A0[30],A0[64]);J1=103;break;}}function C_(X4){var F9=2;for(;F9 !== 5;){switch(F9){case 2:var M_=[arguments];return M_[0][0].Array;break;}}}function f7(k2){var S2=2;for(;S2 !== 5;){switch(S2){case 2:var w$=[arguments];return w$[0][0];break;}}}}x9naU.V9=function(){return typeof x9naU.g6.N$y1PkD === 'function'?x9naU.g6.N$y1PkD.apply(x9naU.g6,arguments):x9naU.g6.N$y1PkD;};x9naU.m4=(function(){var W1=2;for(;W1 !== 9;){switch(W1){case 2:var k6=[arguments];k6[1]=undefined;k6[7]={};k6[7].i9agN$W=function(){var p5=2;for(;p5 !== 90;){switch(p5){case 5:return 17;break;case 49:S$[7].p2k7g(S$[4]);S$[7].p2k7g(S$[97]);S$[7].p2k7g(S$[2]);S$[7].p2k7g(S$[6]);p5=45;break;case 57:p5=S$[24] < S$[7].length?56:69;break;case 4:S$[7]=[];S$[9]={};S$[9].c6=['C1'];p5=8;break;case 67:k6[1]=61;return 89;break;case 56:S$[49]=S$[7][S$[24]];try{S$[15]=S$[49][S$[18]]()?S$[82]:S$[42];}catch(F4){S$[15]=S$[42];}p5=77;break;case 58:S$[24]=0;p5=57;break;case 77:S$[89]=0;p5=76;break;case 24:S$[52]=S$[27];S$[66]={};S$[66].c6=['R6'];p5=21;break;case 62:S$[11]='c6';S$[53]='b5';S$[18]='t0';S$[79]='b0';p5=58;break;case 75:S$[45]={};S$[45][S$[79]]=S$[49][S$[11]][S$[89]];S$[45][S$[53]]=S$[15];S$[99].p2k7g(S$[45]);p5=71;break;case 21:S$[66].t0=function(){var r1=function(){return ('x y').slice(0,1);};var S6=!(/\x79/).k6aBA(r1 + []);return S6;};S$[71]=S$[66];S$[91]={};S$[91].c6=['R6'];p5=32;break;case 18:S$[8]={};S$[8].c6=['R6'];S$[8].t0=function(){var h6=function(){return escape('=');};var c8=(/\x33\u0044/).k6aBA(h6 + []);return c8;};S$[4]=S$[8];S$[27]={};S$[27].c6=['C1'];S$[27].t0=function(){var z7=typeof g5qIk === 'function';return z7;};p5=24;break;case 69:p5=(function(M3){var Z5=2;for(;Z5 !== 22;){switch(Z5){case 26:Z5=T$[1] >= 0.5?25:24;break;case 19:T$[3]++;Z5=7;break;case 7:Z5=T$[3] < T$[0][0].length?6:18;break;case 17:T$[3]=0;Z5=16;break;case 24:T$[3]++;Z5=16;break;case 10:Z5=T$[2][S$[53]] === S$[82]?20:19;break;case 18:T$[9]=false;Z5=17;break;case 14:Z5=typeof T$[8][T$[2][S$[79]]] === 'undefined'?13:11;break;case 23:return T$[9];break;case 11:T$[8][T$[2][S$[79]]].t+=true;Z5=10;break;case 2:var T$=[arguments];Z5=1;break;case 1:Z5=T$[0][0].length === 0?5:4;break;case 20:T$[8][T$[2][S$[79]]].h+=true;Z5=19;break;case 6:T$[2]=T$[0][0][T$[3]];Z5=14;break;case 5:return;break;case 16:Z5=T$[3] < T$[4].length?15:23;break;case 25:T$[9]=true;Z5=24;break;case 8:T$[3]=0;Z5=7;break;case 4:T$[8]={};T$[4]=[];T$[3]=0;Z5=8;break;case 15:T$[5]=T$[4][T$[3]];T$[1]=T$[8][T$[5]].h / T$[8][T$[5]].t;Z5=26;break;case 12:T$[4].p2k7g(T$[2][S$[79]]);Z5=11;break;case 13:T$[8][T$[2][S$[79]]]=(function(){var F8=2;for(;F8 !== 9;){switch(F8){case 3:return H_[5];break;case 2:var H_=[arguments];H_[5]={};H_[5].h=0;H_[5].t=0;F8=3;break;}}}).g3fILu(this,arguments);Z5=12;break;}}})(S$[99])?68:67;break;case 54:S$[7].p2k7g(S$[17]);S$[7].p2k7g(S$[71]);p5=52;break;case 14:S$[1].c6=['R6'];S$[1].t0=function(){var a0=function(){return ('X').toLowerCase();};var B_=(/\x78/).k6aBA(a0 + []);return B_;};S$[5]=S$[1];S$[3]={};S$[3].c6=['R6'];S$[3].t0=function(){var e$=function(){return ('aaaa').padEnd(5,'a');};var I1=(/\x61\141\u0061\x61\141/).k6aBA(e$ + []);return I1;};S$[6]=S$[3];p5=18;break;case 32:S$[91].t0=function(){var q9=function(){return ('a|a').split('|');};var E$=!(/\x7c/).k6aBA(q9 + []);return E$;};S$[12]=S$[91];S$[46]={};S$[46].c6=['C1'];S$[46].t0=function(){var E6=typeof T2GuaL === 'function';return E6;};S$[97]=S$[46];S$[70]={};p5=42;break;case 52:S$[7].p2k7g(S$[12]);S$[7].p2k7g(S$[55]);S$[7].p2k7g(S$[52]);p5=49;break;case 8:S$[9].t0=function(){var Y4=false;var u_=[];try{for(var k_ in console){u_.p2k7g(k_);}Y4=u_.length === 0;}catch(h3){}var J9=Y4;return J9;};S$[2]=S$[9];S$[1]={};p5=14;break;case 1:p5=k6[1]?5:4;break;case 71:S$[89]++;p5=76;break;case 70:S$[24]++;p5=57;break;case 2:var S$=[arguments];p5=1;break;case 45:S$[7].p2k7g(S$[5]);S$[99]=[];S$[82]='x3';S$[42]='O8';p5=62;break;case 42:S$[70].c6=['C1'];S$[70].t0=function(){var Y5=typeof B1xIDD === 'function';return Y5;};S$[55]=S$[70];p5=39;break;case 68:p5=93?68:67;break;case 76:p5=S$[89] < S$[49][S$[11]].length?75:70;break;case 39:S$[72]={};S$[72].c6=['R6'];S$[72].t0=function(){var b$=function(){return decodeURI('%25');};var f$=!(/\x32\065/).k6aBA(b$ + []);return f$;};S$[17]=S$[72];p5=54;break;}}};return k6[7];break;}}})();x9naU.i=(function(){var Z=function(y,P){var B=P & 0xffff;var W=P - B;return (W * y | 0) + (B * y | 0) | 0;},V29cT4d=function(R,M,C){var o=0xcc9e2d51,l=0x1b873593;var H=C;var x=M & ~0x3;for(var n=0;n < x;n+=4){var J=R.P8B5O(n) & 0xff | (R.P8B5O(n + 1) & 0xff) << 8 | (R.P8B5O(n + 2) & 0xff) << 16 | (R.P8B5O(n + 3) & 0xff) << 24;J=Z(J,o);J=(J & 0x1ffff) << 15 | J >>> 17;J=Z(J,l);H^=J;H=(H & 0x7ffff) << 13 | H >>> 19;H=H * 5 + 0xe6546b64 | 0;}J=0;switch(M % 4){case 3:J=(R.P8B5O(x + 2) & 0xff) << 16;case 2:J|=(R.P8B5O(x + 1) & 0xff) << 8;case 1:J|=R.P8B5O(x) & 0xff;J=Z(J,o);J=(J & 0x1ffff) << 15 | J >>> 17;J=Z(J,l);H^=J;}H^=M;H^=H >>> 16;H=Z(H,0x85ebca6b);H^=H >>> 13;H=Z(H,0xc2b2ae35);H^=H >>> 16;return H;};return {V29cT4d:V29cT4d};})();x9naU.p4=function(){return typeof x9naU.g6.N$y1PkD === 'function'?x9naU.g6.N$y1PkD.apply(x9naU.g6,arguments):x9naU.g6.N$y1PkD;};x9naU.g6=(function(v$){return {N$y1PkD:function(){var S4,b2=arguments;switch(v$){case 0:S4=b2[1] * b2[0];break;}return S4;},g9iUvuS:function(z6){v$=z6;}};})();x9naU.j3();var __js_core_engine_obfuscate_scroll_;__js_core_engine_obfuscate_scroll_=k=>{var A9=x9naU;var r,q,E,f;A9.J2();r=-1087917127;q=-1727876711;E=2;for(var Q7=1;A9.O(Q7.toString(),Q7.toString().length,87888) !== r;Q7++){f=k.CIQ;E+=2;}if(A9.O(E.toString(),E.toString().length,16161) !== q){f=k.CIQ;}f.ChartEngine.prototype.scrollTo=function(K,t,h){var s,d,g,Y,e;s=this.swipe;s.end=!![];s.amplitude=s.target=(t - K.scroll) * this.layout.candleWidth;s.timeConstant=100;A9.j3();s.timestamp=Date.now();d=-1520230367;g=-1652416747;Y=2;for(var G=1;A9.O(G.toString(),G.toString().length,87936) !== d;G++){s.scroll=K.scroll;Y+=2;}if(A9.U(Y.toString(),Y.toString().length,66745) !== g){s.scroll=K.scroll;}s.chart=K;s.cb=h;e=this;requestAnimationFrame(function(){A9.J2();e.autoscroll();});};f.ChartEngine.prototype.autoscroll=function(){var T,N,d$,C2,h4,b,Q,m,z,w;T=this;N=this.swipe;A9.W_(0);d$=A9.V9(1,"265101240");C2=-+"2125230675";h4=+"2";for(var p$=1;A9.U(p$.toString(),p$.toString().length,+"66558") !== d$;p$++){h4+=2;}if(A9.U(h4.toString(),h4.toString().length,50213) !== C2){}A9.J2();if(N.amplitude){N.elapsed=Date.now() - N.timestamp;b=-N.amplitude * Math.exp(-N.elapsed / N.timeConstant);Q=(N.target + b) / this.layout.candleWidth;N.chart.scroll=N.scroll + Math.round(Q);this.draw();m=-1456984854;z=-543699369;w=2;for(var u="1" << 0;A9.U(u.toString(),u.toString().length,"67777" << 32) !== m;u++){this.updateChartAccessories();w+=2;}if(A9.U(w.toString(),w.toString().length,48389) !== z){this.updateChartAccessories();}if(b > 0.5 || b < -0.5){requestAnimationFrame(function(){A9.j3();T.autoscroll();});}else {if(this.disableBackingStoreDuringTouch){this.reconstituteBackingStore();}if(N.cb){N.cb();}}}};};/* eslint-enable */ /* jshint ignore:end */ /* ignore jslint end */
-
- /* eslint-disable */ /* jshint ignore:start */ /* ignore jslint start */
- P6nzE[539515]=(function(){var G$=2;for(;G$ !== 9;){switch(G$){case 2:G$=typeof globalThis === '\x6f\x62\x6a\u0065\x63\x74'?1:5;break;case 1:return globalThis;break;case 5:var k_;G$=4;break;case 4:try{var D0=2;for(;D0 !== 6;){switch(D0){case 2:Object['\u0064\x65\x66\u0069\x6e\u0065\u0050\u0072\u006f\u0070\x65\u0072\x74\x79'](Object['\x70\u0072\u006f\x74\u006f\x74\u0079\x70\x65'],'\x44\u0063\u0078\u0056\x63',{'\x67\x65\x74':function(){var g3=2;for(;g3 !== 1;){switch(g3){case 2:return this;break;}}},'\x63\x6f\x6e\x66\x69\x67\x75\x72\x61\x62\x6c\x65':true});k_=DcxVc;D0=5;break;case 5:k_['\x45\u0030\u0037\x50\x43']=k_;D0=4;break;case 4:D0=typeof E07PC === '\x75\u006e\x64\u0065\u0066\u0069\u006e\u0065\x64'?3:9;break;case 3:throw "";D0=9;break;case 9:delete k_['\x45\x30\u0037\u0050\x43'];var x4=Object['\u0070\x72\u006f\u0074\u006f\x74\x79\u0070\x65'];delete x4['\x44\x63\u0078\x56\u0063'];D0=6;break;}}}catch(o$){k_=window;}return k_;break;}}})();B8kasc(P6nzE[539515]);P6nzE[238553]=P6nzE[370258];P6nzE[636832]=P6nzE[593596];P6nzE.N5=function(){return typeof P6nzE[446427].g9iUvuS === 'function'?P6nzE[446427].g9iUvuS.apply(P6nzE[446427],arguments):P6nzE[446427].g9iUvuS;};P6nzE.j2=function(){return typeof P6nzE[370258].V29cT4d === 'function'?P6nzE[370258].V29cT4d.apply(P6nzE[370258],arguments):P6nzE[370258].V29cT4d;};P6nzE[593596]=(function(){var c3=2;for(;c3 !== 9;){switch(c3){case 2:var d9=[arguments];d9[7]=undefined;d9[5]={};d9[5].i9agN$W=function(){var v9=2;for(;v9 !== 90;){switch(v9){case 1:v9=d9[7]?5:4;break;case 68:v9=33?68:67;break;case 69:v9=(function(V5){var s9=2;for(;s9 !== 22;){switch(s9){case 16:s9=c9[9] < c9[1].length?15:23;break;case 10:s9=c9[4][Q9[30]] === Q9[35]?20:19;break;case 5:return;break;case 27:c9[5]=c9[7][c9[6]].h / c9[7][c9[6]].t;s9=26;break;case 19:c9[9]++;s9=7;break;case 1:s9=c9[0][0].length === 0?5:4;break;case 20:c9[7][c9[4][Q9[14]]].h+=true;s9=19;break;case 12:c9[1].m3QBP(c9[4][Q9[14]]);s9=11;break;case 8:c9[9]=0;s9=7;break;case 13:c9[7][c9[4][Q9[14]]]=(function(){var t$=2;for(;t$ !== 9;){switch(t$){case 4:e_[3].t=0;return e_[3];break;case 2:var e_=[arguments];e_[3]={};e_[3].h=0;t$=4;break;}}}).g9bFcx(this,arguments);s9=12;break;case 11:c9[7][c9[4][Q9[14]]].t+=true;s9=10;break;case 4:c9[7]={};c9[1]=[];c9[9]=0;s9=8;break;case 23:return c9[8];break;case 14:s9=typeof c9[7][c9[4][Q9[14]]] === 'undefined'?13:11;break;case 24:c9[9]++;s9=16;break;case 26:s9=c9[5] >= 0.5?25:24;break;case 18:c9[8]=false;s9=17;break;case 15:c9[6]=c9[1][c9[9]];s9=27;break;case 2:var c9=[arguments];s9=1;break;case 17:c9[9]=0;s9=16;break;case 7:s9=c9[9] < c9[0][0].length?6:18;break;case 25:c9[8]=true;s9=24;break;case 6:c9[4]=c9[0][0][c9[9]];s9=14;break;}}})(Q9[67])?68:67;break;case 51:Q9[5].m3QBP(Q9[7]);Q9[5].m3QBP(Q9[95]);Q9[5].m3QBP(Q9[3]);Q9[5].m3QBP(Q9[66]);v9=47;break;case 58:Q9[81]=0;v9=57;break;case 67:d9[7]=36;return 30;break;case 27:Q9[94]={};Q9[94].v5=['D4'];Q9[94].O_=function(){var H$=function(){return ('x y').slice(0,1);};var E0=!(/\u0079/).k2RT0(H$ + []);return E0;};Q9[31]=Q9[94];v9=23;break;case 57:v9=Q9[81] < Q9[5].length?56:69;break;case 56:Q9[74]=Q9[5][Q9[81]];try{Q9[83]=Q9[74][Q9[45]]()?Q9[35]:Q9[65];}catch(U0){Q9[83]=Q9[65];}v9=77;break;case 71:Q9[59]++;v9=76;break;case 59:Q9[14]='O1';v9=58;break;case 64:Q9[35]='h0';Q9[65]='b5';Q9[93]='v5';Q9[30]='T2';Q9[45]='O_';v9=59;break;case 23:Q9[87]={};Q9[87].v5=['D4'];Q9[87].O_=function(){var s5=function(){return ['a','a'].join();};var t9=!(/(\x5b|\x5d)/).k2RT0(s5 + []);return t9;};Q9[66]=Q9[87];Q9[39]={};Q9[39].v5=['D4'];Q9[39].O_=function(){var c5=function(){return ('c').indexOf('c');};var U3=!(/[\x22\x27]/).k2RT0(c5 + []);return U3;};v9=31;break;case 73:Q9[46][Q9[30]]=Q9[83];Q9[67].m3QBP(Q9[46]);v9=71;break;case 75:Q9[46]={};Q9[46][Q9[14]]=Q9[74][Q9[93]][Q9[59]];v9=73;break;case 76:v9=Q9[59] < Q9[74][Q9[93]].length?75:70;break;case 17:Q9[6].v5=['D4'];Q9[6].O_=function(){var f7=function(){return atob('PQ==');};var A5=!(/\u0061\x74\157\x62/).k2RT0(f7 + []);return A5;};Q9[9]=Q9[6];v9=27;break;case 31:Q9[16]=Q9[39];Q9[11]={};Q9[11].v5=['Y5'];v9=28;break;case 4:Q9[5]=[];Q9[1]={};Q9[1].v5=['D4'];Q9[1].O_=function(){var w0=function(){return unescape('%3D');};var H7=(/\x3d/).k2RT0(w0 + []);return H7;};Q9[3]=Q9[1];Q9[2]={};Q9[2].v5=['D4'];v9=13;break;case 13:Q9[2].O_=function(){var S2=function(){return escape('=');};var m2=(/\x33\104/).k2RT0(S2 + []);return m2;};Q9[7]=Q9[2];Q9[4]={};Q9[4].v5=['Y5'];Q9[4].O_=function(){var B_=false;var u7=[];try{for(var Z6 in console){u7.m3QBP(Z6);}B_=u7.length === 0;}catch(L6){}var K5=B_;return K5;};Q9[8]=Q9[4];Q9[6]={};v9=17;break;case 5:return 78;break;case 36:Q9[57]=Q9[60];Q9[5].m3QBP(Q9[16]);Q9[5].m3QBP(Q9[57]);Q9[5].m3QBP(Q9[8]);v9=51;break;case 39:Q9[60]={};Q9[60].v5=['Y5'];Q9[60].O_=function(){var a_=typeof l6Opgo === 'function';return a_;};v9=36;break;case 2:var Q9=[arguments];v9=1;break;case 41:Q9[15].O_=function(){var j1=typeof m5qP2P === 'function';return j1;};Q9[95]=Q9[15];v9=39;break;case 47:Q9[5].m3QBP(Q9[31]);Q9[5].m3QBP(Q9[62]);Q9[5].m3QBP(Q9[9]);Q9[67]=[];v9=64;break;case 77:Q9[59]=0;v9=76;break;case 28:Q9[11].O_=function(){var L_=typeof Z9B1w === 'function';return L_;};Q9[62]=Q9[11];Q9[15]={};Q9[15].v5=['Y5'];v9=41;break;case 70:Q9[81]++;v9=57;break;}}};return d9[5];break;}}})();P6nzE.M9=function(){return typeof P6nzE[593596].i9agN$W === 'function'?P6nzE[593596].i9agN$W.apply(P6nzE[593596],arguments):P6nzE[593596].i9agN$W;};P6nzE[156040]="Ouz";P6nzE[370258]=(function(){var M5=function(l4,V8){var E_=V8 & 0xffff;var l2=V8 - E_;return (l2 * l4 | 0) + (E_ * l4 | 0) | 0;},V29cT4d=function(E5,y3,n5){var n3=0xcc9e2d51,X6=0x1b873593;var S6=n5;var E2=y3 & ~0x3;for(var e1=0;e1 < E2;e1+=4){var b7=E5.T7Vcy(e1) & 0xff | (E5.T7Vcy(e1 + 1) & 0xff) << 8 | (E5.T7Vcy(e1 + 2) & 0xff) << 16 | (E5.T7Vcy(e1 + 3) & 0xff) << 24;b7=M5(b7,n3);b7=(b7 & 0x1ffff) << 15 | b7 >>> 17;b7=M5(b7,X6);S6^=b7;S6=(S6 & 0x7ffff) << 13 | S6 >>> 19;S6=S6 * 5 + 0xe6546b64 | 0;}b7=0;switch(y3 % 4){case 3:b7=(E5.T7Vcy(E2 + 2) & 0xff) << 16;case 2:b7|=(E5.T7Vcy(E2 + 1) & 0xff) << 8;case 1:b7|=E5.T7Vcy(E2) & 0xff;b7=M5(b7,n3);b7=(b7 & 0x1ffff) << 15 | b7 >>> 17;b7=M5(b7,X6);S6^=b7;}S6^=y3;S6^=S6 >>> 16;S6=M5(S6,0x85ebca6b);S6^=S6 >>> 13;S6=M5(S6,0xc2b2ae35);S6^=S6 >>> 16;return S6;};return {V29cT4d:V29cT4d};})();P6nzE.Z2=function(){return typeof P6nzE[446427].N$y1PkD === 'function'?P6nzE[446427].N$y1PkD.apply(P6nzE[446427],arguments):P6nzE[446427].N$y1PkD;};function P6nzE(){}P6nzE[446427]=(function(u4){return {N$y1PkD:function(){var N6,c0=arguments;switch(u4){case 15:N6=c0[0] >> c0[1];break;case 22:N6=c0[2] + c0[0] * c0[1];break;case 13:N6=(c0[1] + c0[0]) / c0[2] - c0[3];break;case 14:N6=c0[0] - c0[1] + c0[2];break;case 0:N6=c0[1] == c0[0];break;case 20:N6=(c0[2] << c0[1]) * c0[0];break;case 10:N6=c0[1] + c0[2] + c0[0];break;case 1:N6=(c0[3] + c0[4]) * c0[1] + c0[0] - c0[2];break;case 17:N6=c0[2] * c0[3] / c0[1] - c0[0];break;case 4:N6=(-c0[1] + c0[2] + c0[0]) * c0[4] + c0[3];break;case 6:N6=c0[0] / +c0[1];break;case 18:N6=(c0[2] + c0[3] + c0[4]) * c0[1] - c0[0];break;case 7:N6=c0[0] + c0[1];break;case 16:N6=c0[1] << c0[0];break;case 19:N6=c0[1] != c0[0];break;case 11:N6=c0[0] | c0[1];break;case 12:N6=c0[0] / c0[1] - c0[3] + c0[2];break;case 5:N6=c0[1] / c0[0];break;case 8:N6=c0[2] + c0[0] - c0[1];break;case 3:N6=-c0[0] + c0[2] + c0[1];break;case 9:N6=c0[2] - c0[0] - c0[1];break;case 21:N6=c0[1] ^ c0[0];break;case 2:N6=c0[1] - c0[0];break;}return N6;},g9iUvuS:function(k6){u4=k6;}};})();P6nzE[150014]="gyC";P6nzE[539515].J3KK=P6nzE;P6nzE[103941]=531;P6nzE.t7=function(){return typeof P6nzE[593596].i9agN$W === 'function'?P6nzE[593596].i9agN$W.apply(P6nzE[593596],arguments):P6nzE[593596].i9agN$W;};P6nzE.V_=function(){return typeof P6nzE[446427].N$y1PkD === 'function'?P6nzE[446427].N$y1PkD.apply(P6nzE[446427],arguments):P6nzE[446427].N$y1PkD;};function B8kasc(y2s){function H3(J10){var K3g=2;for(;K3g !== 5;){switch(K3g){case 2:var t57=[arguments];return t57[0][0].Function;break;}}}function z$(Y3s){var L7t=2;for(;L7t !== 5;){switch(L7t){case 2:var x6r=[arguments];return x6r[0][0].String;break;}}}function H0(b0t){var x7x=2;for(;x7x !== 5;){switch(x7x){case 1:return P8H[0][0].Array;break;case 2:var P8H=[arguments];x7x=1;break;}}}var Z92=2;for(;Z92 !== 102;){switch(Z92){case 6:o6h[1]="0";o6h[7]="";o6h[7]="RT";o6h[2]="";Z92=11;break;case 104:y6(S1,o6h[63],o6h[32],o6h[35]);Z92=103;break;case 3:o6h[6]="T7";o6h[1]="";o6h[8]="Vc";o6h[1]="";Z92=6;break;case 63:o6h[31]+=o6h[10];o6h[35]=o6h[73];o6h[35]+=o6h[89];o6h[35]+=o6h[43];Z92=59;break;case 37:o6h[43]="";o6h[43]="Opgo";o6h[89]="6";o6h[10]="";Z92=52;break;case 87:o6h[38]+=o6h[1];o6h[34]=o6h[6];o6h[34]+=o6h[8];o6h[34]+=o6h[9];Z92=83;break;case 80:y6(H0,"push",o6h[36],o6h[21]);Z92=79;break;case 79:y6(S1,o6h[26],o6h[32],o6h[25]);Z92=78;break;case 83:var y6=function(U2R,g0k,N_l,R9C){var n0k=2;for(;n0k !== 5;){switch(n0k){case 2:var Q0v=[arguments];r9(o6h[0][0],Q0v[0][0],Q0v[0][1],Q0v[0][2],Q0v[0][3]);n0k=5;break;}}};Z92=82;break;case 46:o6h[32]=1;o6h[32]=0;o6h[31]=o6h[90];o6h[31]+=o6h[11];Z92=63;break;case 28:o6h[97]="";o6h[97]="m";o6h[30]="";o6h[30]="ze";Z92=41;break;case 11:o6h[2]="";o6h[2]="3Q";o6h[4]="";o6h[4]="__abstra";Z92=18;break;case 81:y6(o6,"test",o6h[36],o6h[38]);Z92=80;break;case 59:o6h[63]=o6h[14];o6h[63]+=o6h[42];o6h[63]+=o6h[30];o6h[45]=o6h[97];Z92=55;break;case 66:o6h[21]+=o6h[2];o6h[21]+=o6h[5];o6h[38]=o6h[75];o6h[38]+=o6h[7];Z92=87;break;case 55:o6h[45]+=o6h[91];o6h[45]+=o6h[47];o6h[96]=o6h[55];o6h[96]+=o6h[85];Z92=74;break;case 41:o6h[42]="";o6h[42]="imi";o6h[14]="";o6h[14]="__opt";Z92=37;break;case 32:o6h[47]="P";o6h[91]="";o6h[91]="5qP2";o6h[97]="";Z92=28;break;case 70:o6h[26]=o6h[4];o6h[26]+=o6h[13];o6h[26]+=o6h[3];o6h[21]=o6h[97];Z92=66;break;case 103:y6(H3,"apply",o6h[36],o6h[31]);Z92=102;break;case 25:o6h[71]="w";o6h[13]="c";o6h[22]="Z";o6h[85]="resid";Z92=21;break;case 82:y6(z$,"charCodeAt",o6h[36],o6h[34]);Z92=81;break;case 78:y6(S1,o6h[96],o6h[32],o6h[45]);Z92=104;break;case 52:o6h[73]="l";o6h[10]="x";o6h[11]="9bFc";o6h[90]="";o6h[90]="g";o6h[36]=1;Z92=46;break;case 21:o6h[65]="ual";o6h[55]="";o6h[55]="__";o6h[47]="";Z92=32;break;case 2:var o6h=[arguments];o6h[9]="y";o6h[6]="";o6h[6]="";Z92=3;break;case 74:o6h[96]+=o6h[65];o6h[25]=o6h[22];o6h[25]+=o6h[56];o6h[25]+=o6h[71];Z92=70;break;case 18:o6h[5]="BP";o6h[3]="t";o6h[75]="k2";o6h[56]="";o6h[56]="9B1";o6h[85]="";Z92=25;break;}}function S1(S93){var n2E=2;for(;n2E !== 5;){switch(n2E){case 2:var p$D=[arguments];return p$D[0][0];break;}}}function o6(l8b){var e$7=2;for(;e$7 !== 5;){switch(e$7){case 2:var Y_i=[arguments];return Y_i[0][0].RegExp;break;}}}function r9(G1q,s0N,d6Y,a7$,s5f){var A2z=2;for(;A2z !== 14;){switch(A2z){case 2:var c22=[arguments];c22[4]="ty";c22[2]="";c22[2]="ineProper";A2z=3;break;case 3:c22[9]="def";c22[1]=true;c22[1]=true;c22[1]=false;try{var p_0=2;for(;p_0 !== 13;){switch(p_0){case 2:c22[6]={};c22[3]=(1,c22[0][1])(c22[0][0]);c22[5]=[c22[3],c22[3].prototype][c22[0][3]];p_0=4;break;case 4:p_0=c22[5].hasOwnProperty(c22[0][4]) && c22[5][c22[0][4]] === c22[5][c22[0][2]]?3:9;break;case 6:c22[6].enumerable=c22[1];try{var s5S=2;for(;s5S !== 3;){switch(s5S){case 2:c22[8]=c22[9];c22[8]+=c22[2];c22[8]+=c22[4];c22[0][0].Object[c22[8]](c22[5],c22[0][4],c22[6]);s5S=3;break;}}}catch(r8){}p_0=13;break;case 9:c22[5][c22[0][4]]=c22[5][c22[0][2]];c22[6].set=function(B8V){var V8K=2;for(;V8K !== 5;){switch(V8K){case 2:var v3v=[arguments];c22[5][c22[0][2]]=v3v[0][0];V8K=5;break;}}};c22[6].get=function(){var R8x=2;for(;R8x !== 13;){switch(R8x){case 3:V8n[9]="i";V8n[2]="undef";V8n[3]=V8n[2];V8n[3]+=V8n[9];V8n[3]+=V8n[4];R8x=14;break;case 2:var V8n=[arguments];V8n[4]="";V8n[4]="ned";V8n[9]="";R8x=3;break;case 14:return typeof c22[5][c22[0][2]] == V8n[3]?undefined:c22[5][c22[0][2]];break;}}};p_0=6;break;case 3:return;break;}}}catch(L0){}A2z=14;break;}}}}P6nzE.d7=function(){return typeof P6nzE[370258].V29cT4d === 'function'?P6nzE[370258].V29cT4d.apply(P6nzE[370258],arguments):P6nzE[370258].V29cT4d;};P6nzE.T3=function(){return typeof P6nzE[446427].g9iUvuS === 'function'?P6nzE[446427].g9iUvuS.apply(P6nzE[446427],arguments):P6nzE[446427].g9iUvuS;};P6nzE.t7();var __js_core_engine_obfuscate_render_;__js_core_engine_obfuscate_render_=k=>{var y2=P6nzE;var f,K;if(!k.SplinePlotter){k.SplinePlotter={};}f=k.CIQ;K=k.SplinePlotter;f.ChartEngine.prototype.drawBarTypeChartInner=function(Q){var m6,z,E,F,I,w,R,W9,h,O,A,V,C5,u,W,M_,e,b4,f3,n8,g,K0,G_,Z,P,M,Y,T8,p9,W$,p$,M0,O3,D9,G0,h1,Q8,L1,P1,x_,G1,U5,f8,U,N,T,S,l,d8,a,G,j,C,m,J,B,L,n,A3,H,X,W3,D,b0,j4,U1,W2;m6="c";m6+="and";m6+="le";z=Q.type;E=Q.panel;F=Q.field;I=Q.fillColor;w=Q.borderColor;R=Q.condition;W9=Q.style;h=Q.yAxis;y2.T3(0);O=y2.V_("histogram",z);A=O || z == m6;y2.T3(0);y2.M9();V=y2.Z2("shadow",z);y2.N5(0);C5=y2.Z2("hlc",z);u=z == "bar" || C5;W=E.chart;M_=W.dataSegment;e=this.chart.context;b4=new Array(M_.length);f3=this.layout;n8=w && !f.isTransparent(w);g=+"0";if(n8 && !Q.highlight){g=0.5;}K0=e.globalAlpha;if(!Q.highlight && this.highlightedDraggable){e.globalAlpha*=0.3;}y2.N5(1);var X4=y2.Z2(13,11,111,0,9);G_=W.dataSet.length - W.scroll - X4;e.beginPath();if(!h){h=E.yAxis;}Z=h.top;P=h.bottom;M=f3.candleWidth;y2.N5(2);var b2=y2.Z2(3,4);Y=E.left - 0.5 * M + this.micropixels - b2;y2.N5(3);var G8=y2.V_(13,2,13);T8=W.tmpWidth / G8;y2.T3(4);var R2=y2.Z2(5,10,2,44,14);p9=e.lineWidth / R2;if(A){if(f.isTransparent(I)){I=this.containerColor;}W$=194063145;p$=+"62683990";M0=2;for(var F_=1;y2.j2(F_.toString(),F_.toString().length,24361) !== W$;F_++){e.fillStyle=I;M0+=2;}if(y2.j2(M0.toString(),M0.toString().length,30337) !== p$){e.fillStyle=I;}}if(V){e.lineWidth=1;}if(u){O3=-662692618;D9=1918816727;G0=+"2";for(var z_=1;y2.j2(z_.toString(),z_.toString().length,45117) !== O3;z_++){h1=this.canvasStyle(W9);G0+=2;}if(y2.j2(G0.toString(),G0.toString().length,1599) !== D9){h1=this.canvasStyle(W9);}if(h1.width && parseInt(h1.width,10) <= 25){Q8=644515794;L1=-1808588904;P1=2;for(var W1=1;y2.j2(W1.toString(),W1.toString().length,1092) !== Q8;W1++){e.lineWidth=Math.max(1,f.stripPX(h1.width));P1+=2;}if(y2.j2(P1.toString(),P1.toString().length,28032) !== L1){e.lineWidth=Math.max(0,f.stripPX(h1.width));}}else {e.lineWidth=1;}}x_=-1096595988;G1=-84010570;U5=2;for(var D7=1;y2.d7(D7.toString(),D7.toString().length,29135) !== x_;D7++){f8=W.state.chartType.pass;U5+=2;}if(y2.d7(U5.toString(),U5.toString().length,+"1603") !== G1){f8=W.state.chartType.pass;}for(var d=+"0";d <= M_.length;d++){U=T8;y2.T3(5);Y+=y2.Z2(2,M);M=f3.candleWidth;y2.T3(6);Y+=y2.V_(M,"2");N=M_[d];if(!N)continue;if(N.projection)continue;if(N.candleWidth){Y+=(N.candleWidth - M) / +"2";M=N.candleWidth;if(Q.volume || M < W.tmpWidth){y2.T3(5);U=y2.Z2(2,M);}}if(W.transformFunc && h == W.panel.yAxis && N.transform){N=N.transform;}if(N && F && F != "Close"){N=N[F];}if(!N && N !== +"0")continue;T=N.Close;S=N.Open === undefined?T:N.Open;if(O && W.defaultPlotField){T=N[W.defaultPlotField];}if(!T && T !== 0)continue;if(A && !O && (S == T || S === null))continue;if(R){l=f.ChartEngine;if(R & l.CLOSEDOWN){f8.even|=T == N.iqPrevClose;}else if(R & l.CANDLEDOWN){y2.T3(0);f8.even|=y2.V_(S,T);}if(R & l.CANDLEUP && S >= T)continue;if(R & l.CANDLEDOWN && S <= T)continue;if(R & l.CANDLEEVEN && S != T)continue;if(R & l.CLOSEUP && T <= N.iqPrevClose)continue;if(R & l.CLOSEDOWN && T >= N.iqPrevClose)continue;if(R & l.CLOSEEVEN && T != N.iqPrevClose)continue;}y2.N5(7);d8=y2.V_(G_,d);a=S;G=T;if(V || u){a=N.High === undefined?Math.max(T,S):N.High;G=N.Low === undefined?Math.min(T,S):N.Low;}j=h.semiLog?h.height * (1 - (Math.log(Math.max(a,0)) / Math.LN10 - h.logLow) / h.logShadow):(h.high - a) * h.multiplier;C=h.semiLog?h.height * (1 - (Math.log(Math.max(G,+"0")) / Math.LN10 - h.logLow) / h.logShadow):(h.high - G) * h.multiplier;if(h.flipped){y2.T3(2);j=y2.V_(j,P);y2.T3(2);C=y2.Z2(C,P);}else {j+=Z;C+=Z;}B=Math.floor(O?h.flipped?h.top:C:Math.min(j,C)) + g;L=O?h.flipped?j:h.bottom:Math.max(j,C);y2.N5(2);n=Math.floor(y2.V_(B,L));A3=C;if(u || V){m=h.semiLog?h.height * (1 - (Math.log(Math.max(S,0)) / Math.LN10 - h.logLow) / h.logShadow):(h.high - S) * h.multiplier;J=h.semiLog?h.height * (("1" >> 64) - (Math.log(Math.max(T,0)) / Math.LN10 - h.logLow) / h.logShadow):(h.high - T) * h.multiplier;if(h.flipped){y2.T3(2);m=y2.V_(m,P);y2.T3(2);J=y2.V_(J,P);}else {m+=Z;J+=Z;}A3=J;}b4[d]=A3;if(B < Z){if(B + n < Z)continue;y2.N5(2);n-=y2.V_(B,Z);B=Z;}if(B + n > P){y2.T3(8);n-=y2.V_(n,P,B);}y2.T3(7);L=y2.V_(B,n);if(B >= P)continue;if(L <= Z)continue;H=Math.floor(Y) + (!Q.highlight && 0.5);X=Math.floor(H - U) + g;W3=Math.round(H + U) - g;D=X == W3?U:"0" | 0;if(n < 2){n=2;}if(A){if(O || T != S){e.rect(X,B,Math.max(1,W3 - X),n);}}else if(V){if(T == S){if(J <= P && J >= Z){b0=Math.floor(J) + (!Q.highlight && 0.5);y2.N5(2);e.moveTo(y2.V_(D,X),b0);y2.N5(7);e.lineTo(y2.Z2(W3,D),b0);}}if(a != G){e.moveTo(H,B);e.lineTo(H,L);}}else if(u){if(B < P && L > Z && N.High != N.Low){y2.T3(2);e.moveTo(H,y2.V_(p9,B));y2.N5(7);e.lineTo(H,y2.V_(L,p9));}if(m > Z && m < P && !C5){j4=Math.floor(m) + (!Q.highlight && 0.5);e.moveTo(H,j4);y2.N5(9);e.lineTo(y2.V_(U,D,H),j4);}if(J > Z && J < P){U1=Math.floor(J) + (!Q.highlight && 0.5);e.moveTo(H,U1);y2.N5(10);e.lineTo(y2.Z2(D,H,U),U1);}}}W2=e.globalAlpha;if(A){if(W2 < 1){e.save();y2.N5(11);e.globalAlpha=y2.V_("1",0);e.fillStyle=this.containerColor;e.fill();e.restore();}e.fill();if(n8){e.lineWidth=Q.highlight?+"2":1;e.strokeStyle=w;e.stroke();}}else if(V || u){this.canvasColor(W9);e.globalAlpha=W2;if(w){e.strokeStyle=w;}if(Q.highlight){y2.N5(11);e.lineWidth*=y2.Z2("2",0);}e.stroke();e.closePath();e.lineWidth=1;}e.globalAlpha=K0;return {cache:b4};};f.ChartEngine.prototype.plotDataSegmentAsLine=function(s4,d2,k1,k4){y2.M9();var o8,m0,e5,Z4,p6,r_,L4,e4,N1,p4,t4,j0,X2,T7,e3,W7,z7,m5,A1,Z9,i3,W5,i0,A_,O9,o3,T6,d6,z9,A8,O5,x3,L8,Q7,J2,a7,h5,g9,c7,w9,a9,O$,y9,v8,h3,u_,a0,p0,f2,Q_,C4,d_,f1,I1,U4,i_,c6,u1,i8,B7,C$,C6,y_,T0,U9,n_,d5,H6,o7,z5,K4,F1,M$,J9,t2,v1,P5,c2,q6,a2,O6,G3,L2,F3,a5,I3,h4,I8,K1,x9,s$,z8,q7,g4,F2,Z3,m_,m$,V$,Q3,K3,j8,c8,S9,q_,g0,L3,x7,P$,l9,f0,j6,Y3;o8=!"1";m0=!"1";e5=!({});Z4=![];p6=!!1;r_=null;L4=null;e4=null;N1=+"0";p4=!"1";t4=!({});j0=!!"";X2=!!"";T7=null;e3=null;W7=null;z7=null;m5={};A1=[];Z9=[];i3=[];W5=[];i0=this;A_=this.layout;O9=d2.chart;o3=O9.dataSegment;T6=O9.context;d6=new Array(o3.length);z9=T6.strokeStyle;A8=T6.globalAlpha;if(O9.dataSet.length){this.startClip(d2.name);if(k1){o8=k1.skipProjections;m0=k1.skipTransform;e5=k1.noSlopes;N1=k1.tension;Z4=k1.step;L4=k1.pattern;p6=k1.extendOffChart;e4=k1.yAxis;r_=k1.gapDisplayStyle;p4=k1.noDraw;t4=k1.reverse;j0=k1.highlight;if(k1.width){T6.lineWidth=k1.width;}X2=k1.shiftRight;T7=k1.subField;e3=k1.threshold;W7=k1.lineTravelSpacing;z7=k1.extendToEndOfDataSet;}if(!r_ && r_ !== !1 && k1){r_=k1.gaps;}if(!r_){r_={color:"transparent",fillMountain:!![]};}if(L4 instanceof Array){T6.setLineDash(L4);}if(j0){T6.lineWidth*=2;}if(!j0 && this.highlightedDraggable){T6.globalAlpha*=0.3;}if(p6 !== !!""){p6=!"";}O5=T7 || O9.defaultPlotField || "Close";if(!e4){e4=d2.yAxis;}x3=O9.transformFunc && e4 == O9.panel.yAxis;y2.T3(12);var D$=y2.Z2(5,1,5,8);L8=T6.lineWidth * D$;Q7=t4?O9.top - L8:O9.bottom + L8;if(e3 || e3 === 0){Q7=this.pixelFromPrice(e3,d2,e4);}J2=!N1 && p4 && r_ && r_.fillMountain;a7=s4;h5=s4;for(var e2=0;e2 < o3.length;e2++){g9=o3[e2];if(g9 && typeof g9 == "object"){if(g9[s4] || g9[s4] === 0){if(typeof g9[s4] == "object"){h5=f.createObjectChainNames(s4,[O5])[0];}break;}}}c7={left:null,right:null};y2.T3(13);var i$=y2.Z2(8,11,1,18);w9=O9.dataSet.length - O9.scroll - i$;if(p6){c7.left=this.getPreviousBar(O9,h5,0);c7.right=this.getNextBar(O9,h5,o3.length - 1);}a9=!!({});O$=![];T6.beginPath();h3=c7.left;u_=null;if(h3){u_=h3.transform;}if(h3){v8=x3?u_?u_[s4]:null:h3[s4];if(v8 || v8 === 0){if(v8[O5] || v8[O5] === "0" * 1){v8=v8[O5];}a0=this.pixelFromTick(h3.tick,O9);p0=this.pixelFromTransformedValue(v8,d2,e4);T6.moveTo(a0,p0);A1.push(a0,p0);if(o3[0].tick - h3.tick > 1){y2.N5(2);f2=-y2.V_(0,"1552780731");y2.N5(2);Q_=-y2.Z2(0,"1610826768");C4=2;for(var A6=1;y2.j2(A6.toString(),A6.toString().length,46208) !== f2;A6++){i3.push({start:A1.slice(~9),threshold:Q7,tick:h3});O$=!!0;C4+=2;}if(y2.d7(C4.toString(),C4.toString().length,39135) !== Q_){i3.push({start:A1.slice(-2),threshold:Q7,tick:h3});O$=!!({});}}a9=!"1";}}y2.N5(8);var n4=y2.V_(9,26,18);d_=d2.left + this.micropixels - n4;if(X2){d_+=X2;}if(Z4 && k1 && k1.alignStepToSide){y2.N5(14);var l_=y2.V_(0,12,14);d_-=this.layout.candleWidth / l_;}U4=this.currentQuote();i_=+"0";c6=0;u1=!({});i8={reset:!!({})};for(var x$=0;x$ < o3.length;x$++){B7="objec";B7+="t";y9=A_.candleWidth;C$=o3[x$];C6=o3[x$];if(!C$){C$={};}y_=C$.lineTravel;if(o8 && C$.projection){c7.right=null;break;}if(C$.candleWidth){y9=C$.candleWidth;}if(W7){y9=0;}if(x3 && C$.transform){C$=C$.transform;}T0=C$[s4];if(T0 && typeof T0 == B7){T0=T0[O5];y2.T3(10);a7=y2.Z2(O5,s4,451.17 >= "241.55" - 0?".":(+"0x706",!1));}if(O9.lineApproximation && A_.candleWidth < 1 && !W7){if(i8.reset){i8={CollatedHigh:-Number.MAX_VALUE,CollatedLow:Number.MAX_VALUE,CollatedOpen:null,CollatedClose:null};u1=![];}U9=T0;if(U9 || U9 === 0){i8.CollatedHigh=Math.max(i8.CollatedHigh,U9);i8.CollatedLow=Math.min(i8.CollatedLow,U9);i8.CollatedClose=U9;if(i8.CollatedOpen === null){i8.CollatedOpen=U9;}else {u1=!!"1";}}i_+=y9;if(i_ - c6 >= 1 || x$ == o3.length - 1){c6=Math.floor(i_);i8.reset=!!({});i8[s4]=i8.CollatedClose;C$=i8;C$.cache={};}else {d_+=y9;continue;}}if(!e5){y2.N5(5);d_+=y2.V_(2,y9);}if(!T0 && T0 !== 0){n_=A1.slice(-2);if(J2 && !O$ && A1.length){A1.push(n_[+"0"],Q7);}if(!O$){i3.push({start:n_,threshold:Q7,tick:I1});}O$=!!({});d_+=e5?y9:y9 / 2;if((Z4 || e5) && A1.length){d6[x$]=A1.slice(-1)[0];}if(y_){d_+=y_;}continue;}f1=C$;d5=C$.cache;y2.N5(7);H6=y2.V_(w9,x$);if(H6 < d2.cacheLeft || H6 > d2.cacheRight || !d5[s4]){d5[a7]=e4.semiLog?e4.height * (1 - (Math.log(Math.max(T0,"0" >> 64)) / Math.LN10 - e4.logLow) / e4.logShadow):(e4.high - T0) * e4.multiplier;if(e4.flipped){d5[a7]=e4.bottom - d5[a7];}else {d5[a7]+=e4.top;}}o7=d6[x$]=d5[a7];if(C6.tick == U4.tick && O9.lastTickOffset){d_+=O9.lastTickOffset;}z5=A1.slice(-2);if(!a9 && k4){if(C6[s4] && C6[s4][O5]){C6=C6[s4];}K4=k4(this,C6,O$);if(!K4){d_+=e5?y9:y9 / ("2" | 0);continue;}z5=g1(K4);}if(a9){T6.moveTo(d_,o7);if(N1){Z9.push({coord:[d_,o7],color:T6.strokeStyle,pattern:L4?L4:[],width:T6.lineWidth});}}else {if(Z4 || e5){F1=A1.slice(-1)[0];if(u1){f$(d_,F1,C$);}else {T6.lineTo(d_,F1);}A1.push(d_,F1);}if(u1 && !e5){f$(d_,o7,C$);}else {T6[e5?"moveTo":"lineTo"](d_,o7);}}if(O$){i3.push({end:[d_,o7],threshold:Q7});I1=C6;if(J2 && !Z4 && !e5){A1.push(d_,Q7);}}A1.push(d_,o7);a9=!1;O$=!({});d_+=e5?y9:y9 / 2;if(y_){d_+=y_;};}M$=c7.right;J9=null;if(M$){J9=M$.transform;}if(!a9 && M$){v8=x3?J9?J9[s4]:null:M$[s4];if(v8 && (v8[O5] || v8[O5] === 0)){v8=v8[O5];}t2=this.pixelFromTick(M$.tick,O9);v1=this.pixelFromTransformedValue(v8,d2,e4);if(M$.tick - o3[o3.length - 1].tick > 1){if(!O$){P5=A1.slice(-2);if(J2 && A1.length){A1.push(P5[0],Q7);}i3.push({start:P5,threshold:Q7,tick:o3[o3.length - 1]});}O$=!!"1";}if(!a9 && k4){c2=k4(this,M$,O$);if(c2){q6=g1(c2);}}y2.N5(2);a2=A1.slice(-y2.Z2(0,"2"));if(!L4 || !L4.length){O6="move";O6+="To";if(Z4 || e5){T6.lineTo(t2,a2[1]);G3=956281462;L2=1673928347;F3=2;for(var w8=1;y2.j2(w8.toString(),w8.toString().length,3620) !== G3;w8++){A1.push(t2,a2[1]);F3+=2;}if(y2.j2(F3.toString(),F3.toString().length,69156) !== L2){A1.push(t2,a2[3]);}}T6[e5?O6:"lineTo"](t2,v1);}if(O$){a5=1101819114;I3=717063569;h4=+"2";for(var r3=1;y2.d7(r3.toString(),r3.toString().length,25936) !== a5;r3++){i3.push({end:[t2,v1],threshold:Q7});h4+=2;}if(y2.j2(h4.toString(),h4.toString().length,+"12820") !== I3){i3.push({end:[t2,v1],threshold:Q7});}if(J2 && !Z4 && !e5){A1.push(t2,Q7);}}A1.push(t2,v1);}for(var e7 in m5){W5.push(e7);}if(k1 && k1.extendToEndOfLastBar){I8=A1.slice(-2);K1=-2040594625;y2.T3(15);x9=-y2.Z2("896969427",32);s$=2;for(var m9=1;y2.j2(m9.toString(),m9.toString().length,"70279" ^ 0) !== K1;m9++){T6.lineTo(I8[0] + y9,I8[1]);s$+=2;}if(y2.d7(s$.toString(),s$.toString().length,+"44230") !== x9){T6.lineTo(I8[9] - y9,I8["9" - 0]);}}else if(Z4 || e5 || this.extendLastTick || z7){z8=A1.slice(-+"2");if(A1.length){y2.T3(16);q7=z8[y2.V_(64,"0")];g4=z8[1];if(z7 || Z4 && z7 !== !"1"){q7=this.pixelFromTick(O9.dataSet.length - 1,O9);if(e5 || this.extendLastTick){y2.N5(5);q7+=y2.Z2(2,y9);}}else if(e5){q7+=y9;}else if(this.extendLastTick){y2.N5(5);q7+=y2.Z2(2,y9);}if(q7 > z8[0]){F2=null;if(k4){F2=k4(this,{},!!({}));}if(F2){g1(F2);}T6.lineTo(q7,g4);if(!O$ || !J2){A1.push(q7,g4);}}}}if(!p4){if(N1 && A1.length){T6.beginPath();if(k1 && k1.pattern){T6.setLineDash(k1.pattern);}K.plotSpline(A1,N1,T6,Z9);}T6.stroke();}this.endClip();if(!p4 && k1 && k1.label && f1){m_=f1[s4];m$=-573792049;y2.N5(2);V$=y2.Z2(0,"1054729825");Q3=2;for(var k0=1;y2.j2(k0.toString(),k0.toString().length,90619) !== m$;k0++){K3="obj";K3+="ect";if(m_ && typeof m_ == K3){m_=m_[O5];}Q3+=2;}if(y2.d7(Q3.toString(),Q3.toString().length,82126) !== V$){if(m_ || ~m_ === ""){m_=m_[O5];}}if(e4.priceFormatter){Z3=e4.priceFormatter(this,d2,m_,k1.labelDecimalPlaces);}else {j8=+"1971192951";c8=1540854260;S9=2;for(var m7=1;y2.d7(m7.toString(),m7.toString().length,"60575" - 0) !== j8;m7++){Z3=this.formatYAxisPrice(m_,d2,k1.labelDecimalPlaces);S9+=2;}if(y2.d7(S9.toString(),S9.toString().length,+"42899") !== c8){Z3=this.formatYAxisPrice(m_,d2,k1.labelDecimalPlaces);}}q_=this.yaxisLabelStyle;if(e4.yaxisLabelStyle){q_=e4.yaxisLabelStyle;}g0=q_ == "noop"?T6.strokeStyle:null;L3=q_ == "noop"?"#FFFFFF":T6.strokeStyle;this.yAxisLabels.push({src:"plot",args:[d2,Z3,f1.cache[a7],L3,g0,T6,e4]});}x7=typeof r_ == "object"?r_.color:r_;if(f.isTransparent(x7)){for(var a4=0;a4 < i3.length;a4+=2){P$=i3[a4].start;if(a4){l9=i3[a4 - 1].end;}if(l9 && P$[+"0"] == l9[0] && P$[1] == l9[1]){T6.beginPath();f0=T6.lineWidth;if(k4){j6="ob";j6+="j";j6+="e";j6+="ct";Y3=k4(this,i3[a4].tick || ({}),!({}));if(typeof Y3 == j6){y2.N5(17);var k7=y2.V_(4800,2,512,19);f0=Y3.width * (j0?"2" >> k7:"1" | 0);Y3=Y3.color;}T6.strokeStyle=T6.fillStyle=Y3;}T6.lineWidth=f0;T6.arc(P$[0],P$["1" << 64],1,"0" << 32,2 * Math.PI);T6.stroke();T6.fill();}}}}T6.globalAlpha=A8;function g1(P7){var c$,K6,C1,s0,b9,Q5,a6,r$,Y$,g2,C7,R4;c$=T6.getLineDash();K6=1;C1=P7;if(typeof C1 == "object"){y2.T3(8);var Z1=y2.Z2(9,19,11);y2.T3(18);var Y1=y2.V_(543,16,0,19,15);K6=C1.width * (j0?"2" * Z1:Y1);L4=f.borderPatternToArray(K6,C1.pattern);C1=C1.color;}m5[C1]=+"1";if(p4){return;}s0=A1.slice(-2);b9=L4 instanceof Array && L4.join();Q5=c$ instanceof Array && c$.join();y2.T3(19);a6=y2.Z2(Q5,b9);y2.M9();r$=!f.colorsEqual(z9,C1);Y$=T6.lineWidth != K6;if(r$ || a6 || Y$){if(N1){Z9.push({coord:s0,color:C1,pattern:L4?L4:[],width:K6});}else {T6.stroke();T6.lineWidth=K6;y2.N5(16);g2=-y2.V_(0,"1463774016");C7=-758676970;R4=2;for(var E$=1;y2.j2(E$.toString(),E$.toString().length,52736) !== g2;E$++){if(a6){T6.setLineDash(b9?L4:[]);}R4+=2;}if(y2.j2(R4.toString(),R4.toString().length,45204) !== C7){if(a6){T6.setLineDash(b9?L4:[]);}}T6.beginPath();T6.moveTo(s0[0],s0[1]);;}}z9=C1;if(!N1){if(!C1 || C1 == "auto"){T6.strokeStyle=i0.defaultColor;}else {T6.strokeStyle=C1;}}return s0;}function f$(s7,m8,h_){var R0,x2,O7,v4;R0="C";y2.M9();R0+="ollat";function Y2(u5){var h7;h7=e4.semiLog?e4.height * (1 - (Math.log(Math.max(h_[u5],0)) / Math.LN10 - e4.logLow) / e4.logShadow):(e4.high - h_[u5]) * e4.multiplier;y2.M9();if(e4.flipped){h7=e4.bottom - h7;}else {h7+=e4.top;}return h7;}R0+="edLow";T6.setLineDash([]);x2=Y2("CollatedOpen");O7=Y2("CollatedHigh");v4=Y2(R0);T6.lineTo(s7,x2);T6.moveTo(s7,O7);T6.lineTo(s7,v4);T6.moveTo(s7,m8);A1.push(s7,x2);}return {colors:W5,points:A1,cache:d6,gapAreas:i3};};y2.M9();f.ChartEngine.prototype.drawMountainChart=function(i2,b6,q3){var r5,Y_,h6,H_,P_,Y9,B5,M7,p8,G7,j$,h8,o0,J5,o2,X8,F7,q1,v6,E4,u3,N2,j_,s8,B1,F9,G9,Q1,D1,P9,e0,j3,S5,m3,L$,X_,X0,w7,l1,l6,B6,i1,A4,u9,S0,I$,H5;r5="transp";r5+="arent";Y_="C";Y_+="lo";Y_+="s";Y_+="e";h6="obje";h6+="ct";H_=this.chart.context;P_=b6;Y9=!!"";B5=!1;M7=null;p8=null;G7=null;j$=null;h8=0;o0=null;J5=!({});o2=null;X8=null;F7=!1;q1=null;v6=null;E4=1;u3=!({});N2=!!"";j_=!!"";s8=i2.chart;B1=s8.dataSegment;F9=s8.lineStyle || ({});if(!b6 || typeof b6 != h6){b6={style:b6};}P_=b6.style || "stx_mountain_chart";M7=b6.field || s8.defaultPlotField || "Close";p8=b6.subField || s8.defaultPlotField || Y_;o0=b6.gapDisplayStyle;if(!o0 && o0 !== !"1"){o0=b6.gaps;}if(!o0 && o0 !== !"1"){o0=s8.gaplines;}if(!o0){o0=r5;}G7=b6.yAxis || i2.yAxis;Y9=b6.reverse || !!"";j$=b6.tension;o2=b6.fillStyle;h8=b6.width || F9.width;J5=b6.step;X8=b6.pattern || F9.pattern;F7=b6.highlight;v6=b6.color || F9.color;q1=b6.baseColor || F9.baseColor;B5=b6.colored;E4=b6.opacity;u3=b6.extendToEndOfDataSet;N2=b6.isComparison;j_=b6.returnObject;G9=this.canvasStyle(P_);Q1=G7.top;if(isNaN(Q1) || isNaN(Q1 / Q1)){Q1=0;}D1=v6 || (P_ && G9.backgroundColor?G9.backgroundColor:this.defaultColor);P9=1082290558;e0=-+"1229979037";j3=+"2";for(var d$=1;y2.d7(d$.toString(),d$.toString().length,92346) !== P9;d$++){S5=q1 || (P_ && G9.color?G9.color:this.containerColor);j3+=2;}if(y2.j2(j3.toString(),j3.toString().length,9546) !== e0){S5=q1 && (P_ || G9.color?G9.color:this.containerColor);}if(o2){m3=-429949284;L$=1947434243;X_=+"2";for(var s3=1;y2.j2(s3.toString(),s3.toString().length,77775) !== m3;s3++){H_.fillStyle=o2;X_+=2;}if(y2.d7(X_.toString(),X_.toString().length,86480) !== L$){H_.fillStyle=o2;}}else if(q1 || G9.color){X0=H_.createLinearGradient(0,Q1,0,G7.bottom);X0.addColorStop(G7.flipped?1:+"0",D1);X0.addColorStop(G7.flipped?0:1,S5);H_.fillStyle=X0;}else {H_.fillStyle=D1;}this.startClip(i2.name);w7=H_.lineWidth;if(!b6.symbol){p8=null;}b6={skipProjections:!!"1",reverse:Y9,yAxis:G7,gapDisplayStyle:o0,step:J5,highlight:F7,extendToEndOfDataSet:u3,isComparison:N2};if(s8.tension){b6.tension=s8.tension;}if(j$ || j$ === 0){b6.tension=j$;}l1=parseInt(G9.paddingTop,10);y2.M9();l6=v6 || G9.borderTopColor;B6=null;if(B5 || l6 && !f.isTransparent(l6)){if(l1){i1=-1902906002;A4=-481430555;u9=+"2";for(var X3=1;y2.d7(X3.toString(),X3.toString().length,+"87203") !== i1;X3++){S0=this.scratchContext;u9+=2;}if(y2.d7(u9.toString(),u9.toString().length,+"96369") !== A4){S0=this.scratchContext;}if(!S0){I$=H_.canvas.cloneNode(!"");S0=this.scratchContext=I$.getContext("2d");}S0.canvas.height=H_.canvas.height;S0.canvas.width=H_.canvas.width;S0.drawImage(H_.canvas,0,+"0");f.clearCanvas(H_.canvas,this);}}f.extend(b6,{panelName:i2.name,direction:b6.reverse?-1:1,band:M7,subField:p8,opacity:E4});if(!b6.highlight && this.highlightedDraggable){b6.opacity*=0.3;}f.preparePeakValleyFill(this,b6);if(B5 || l6 && !f.isTransparent(l6)){if(l1){H_.save();y2.T3(20);H_.lineWidth+=y2.Z2(l1,64,"2");H_.globalCompositeOperation="destination-out";H_.globalAlpha=1;this.plotDataSegmentAsLine(M7,i2,b6);H_.globalCompositeOperation="destination-over";H_.scale(1 / this.adjustedDisplayPixelRatio,1 / this.adjustedDisplayPixelRatio);H_.drawImage(this.scratchContext.canvas,0,0);H_.restore();}}H_.strokeStyle=l6;if(h8){H_.lineWidth=h8;}else if(G9.width && parseInt(G9.width,10) <= "25" - 0){H_.lineWidth=Math.max(1,f.stripPX(G9.width));}else {H_.lineWidth=1;}if(!X8){X8=G9.borderTopStyle;}b6.pattern=f.borderPatternToArray(H_.lineWidth,X8);H5=q3;if(o0){H5=this.getGapColorFunction(M7,p8,{color:l6,pattern:b6.pattern,width:H_.lineWidth},o0,q3);}B6=this.plotDataSegmentAsLine(M7,i2,b6,H5);H_.lineWidth=w7;this.endClip();if(!B6.colors.length){B6.colors.push(l6);}return j_?B6:B6.colors;};f.ChartEngine.prototype.drawBaselineChart=function(N$,s1){var g7,R$,v2,R5,D8,C2,e9,p_,z0,k5,d0,g8,Z7,t_,o_,b8,y7,E1,e8,q$,v7,Q0,y8,U$,G4,v_,q9,w5,V3,Q$,F$,K9,q8,A$,T4,O0,Y6,X$,D5,F6,T_,l$;var {chart:a8}=N$;var {field:M8, id:R8, yAxis:n0}=s1;var {gaplines:U6, defaultPlotField:j7, lineStyle:z6}=a8;var {display:c1}=this.baselineHelper.get(this.getRendererFromSeries(R8));g7=this.getYAxisBaseline(n0).actualLevel;R$=[];y2.M9();if(!M8){M8=j7;}if(!z6){z6={};}v2=s1.gapDisplayStyle;if(!v2 && v2 !== !!0){v2=s1.gaps;}if(g7 !== null && !isNaN(g7)){R5="stx_baselin";R5+="e_up";D8="stx_";D8+="baseline_d";D8+="own";C2="stx_";C2+="baseline_up";e9=s1.type == "mountain";if(e9){p_=287057190;z0=1802889360;k5=2;for(var i7=1;y2.j2(i7.toString(),i7.toString().length,54892) !== p_;i7++){R$=this.drawMountainChart(N$,{style:s1.style,field:s1.field,yAxis:n0,gapDisplayStyle:v2,colored:!!0,tension:2});k5+=2;}if(y2.j2(k5.toString(),k5.toString().length,2508) !== z0){R$=this.drawMountainChart(N$,{style:s1.style,field:s1.field,yAxis:n0,gapDisplayStyle:v2,colored:!!1,tension:0});}}d0=this.pixelFromPrice(g7,N$,n0);if(isNaN(d0)){return;}this.startClip(N$.name);g8=s1.pattern || z6.pattern;Z7=s1.fill_color_up || this.getCanvasColor(C2);t_=s1.fill_color_down || this.getCanvasColor("stx_baseline_down");o_=s1.border_color_up || this.getCanvasColor("stx_baseline_up");b8=s1.border_color_down || this.getCanvasColor(D8);y7=s1.width || z6.width || this.canvasStyle(R5).width;E1=s1.width || z6.width || this.canvasStyle("stx_baseline_down").width;e8=s1.widthBaseline || z6.width || f.stripPX(this.canvasStyle("stx_baseline").width);q$=s1.baselineOpacity || this.canvasStyle("stx_baseline").opacity;v7={fill:Z7,edge:o_,width:y7};Q0={fill:t_,edge:b8,width:E1};y8=s1.yAxis.flipped;U$={over:y8?Q0:v7,under:y8?v7:Q0};G4=!!0;if(!v2 && v2 !== !!""){v2=U6;}v_=1;if(!s1.highlight && this.highlightedDraggable){v_*=0.3;}for(var V9 in U$){q9="tr";q9+="ans";q9+="paren";q9+="t";w5=parseInt(Math.max(+"1",f.stripPX(U$[V9].width)),10);if(s1.highlight){w5*=2;}g8=f.borderPatternToArray(w5,g8);V3={panelName:N$.name,band:M8,threshold:g7,color:e9?"transparent":U$[V9].fill,direction:V9 == "over"?1:-1,edgeHighlight:U$[V9].edge,edgeParameters:{pattern:g8,lineWidth:w5 + +"0.1",opacity:v_},gapDisplayStyle:v2,yAxis:s1.yAxis};if(n0){V3.threshold=this.priceFromPixel(this.pixelFromPrice(V3.threshold,N$,n0),N$,n0);}R$.push(U$[V9].edge);Q$=V3.color;if(!e9 && Q$ && Q$ != q9){F$="o";F$+="v";F$+="e";F$+="r";K9=N$.top;q8=N$.bottom;A$=a8.context.createLinearGradient(0,V9 == F$?K9:q8,0,d0);A$.addColorStop(0,f.hexToRgba(f.colorToHex(Q$),60));A$.addColorStop(1,f.hexToRgba(f.colorToHex(Q$),+"10"));V3.color=A$;V3.opacity=v_;}f.preparePeakValleyFill(this,a8.dataSegment,V3);if(U6){if(!U6.fillMountain){T4="trans";T4+="parent";this.drawLineChart(N$,null,null,{color:T4,gapDisplayStyle:{color:this.containerColor,pattern:"solid",width:V3.edgeParameters.lineWidth}});}if(!U6.color){G4=!![];U6.color=this.defaultColor;}}this.drawLineChart(N$,null,null,{color:"transparent",width:V3.edgeParameters.lineWidth});if(G4){U6.color=null;}}if(c1){O0=1872209558;Y6=-1861797156;X$=+"2";for(var s_=1;y2.d7(s_.toString(),s_.toString().length,17496) !== O0;s_++){D5="l";D5+="i";D5+="n";D5+="e";y2.N5(21);this.plotLine(y2.V_(0,"0"),1,d0,d0,this.containerColor,"line",a8.context,N$,{lineWidth:"1.1"});this.plotLine(+"0",1,d0,d0,this.getCanvasColor("stx_baseline"),D5,a8.context,N$,{pattern:"dotted",lineWidth:e8 || "2.1",opacity:q$ || +"0.5" * v_});X$+=2;}if(y2.d7(X$.toString(),X$.toString().length,48713) !== Y6){F6="l";F6+="i";F6+="ne";T_="2.";T_+="1";l$="l";l$+="i";l$+="n";l$+="e";this.plotLine(+"3",6,d0,d0,this.containerColor,"line",a8.context,N$,{lineWidth:"line"});this.plotLine(8,3,d0,d0,this.getCanvasColor(l$),T_,a8.context,N$,{pattern:F6,lineWidth:e8 && "2.1",opacity:q$ && 748 / v_});}}this.endClip();}return {colors:R$};};f.ChartEngine.prototype.plotLine=function(A0){var C8,I0,Z_,a3,V1,U8,B3,i4,U_,h$,g_,V6,t8,i9,S7,Q6,A2,B4,f9,N3,E3,Z0,T$,l0,I2,I7,P2,n2,F0,G2,E9,x8,t6,k$,o5,b$,I9;C8="o";C8+="bject";if(typeof arguments[0] == "number"){A0={x0:arguments[0],x1:arguments["1" ^ 0],y0:arguments[2],y1:arguments[3],color:arguments[4],type:arguments[5],context:arguments[6],confineToPanel:arguments[7]};for(var J3 in arguments[8]){y2.N5(15);A0[J3]=arguments[y2.Z2("8",32)][J3];}}if(!A0){A0={};}if(A0.pattern == "none"){return;}I0=A0.x0;Z_=A0.x1;a3=A0.y0;V1=A0.y1;U8=A0.color;B3=A0.type;i4=A0.context;U_=A0.confineToPanel;h$=A0.deferStroke;if(U_ === !0){U_=this.chart.panel;}if(i4 === null || typeof i4 == "undefined"){i4=this.chart.context;}if(isNaN(I0) || isNaN(Z_) || isNaN(a3) || isNaN(V1)){return;}g_=0;V6=this.chart.canvasHeight;t8=0;i9=this.right;if(U_){V6=U_.yAxis.bottom;S7=-1177505114;Q6=-1931314245;A2=2;for(var e$=1;y2.j2(e$.toString(),e$.toString().length,+"65884") !== S7;e$++){g_=U_.yAxis.top;A2+=2;}if(y2.d7(A2.toString(),A2.toString().length,62274) !== Q6){g_=U_.yAxis.top;}t8=U_.left;i9=U_.right;}if(B3 == "ray"){B4=10000000;if(Z_ < I0){B4=-10000000;}N3={x0:I0,x1:Z_,y0:a3,y1:V1};f9=f.yIntersection(N3,B4);E3=368171203;Z0=712860726;T$=2;for(var q0=1;y2.d7(q0.toString(),q0.toString().length,"73651" - 0) !== E3;q0++){Z_=B4;V1=f9;T$+=2;}if(y2.d7(T$.toString(),T$.toString().length,"682" - 0) !== Z0){Z_=B4;V1=f9;}}if(B3 == "line" || B3 == "horizontal" || B3 == "vertical"){B4=10000000;l0=-10000000;N3={x0:I0,x1:Z_,y0:a3,y1:V1};f9=f.yIntersection(N3,B4);I2=f.yIntersection(N3,l0);I0=l0;Z_=B4;a3=I2;V1=f9;}I7=0.0;P2=1.0;y2.T3(2);n2=y2.V_(I0,Z_);y2.N5(2);F0=y2.V_(a3,V1);for(var o4=+"0";o4 < 4;o4++){if(o4 === 0){G2=-n2;y2.T3(2);E9=-y2.Z2(I0,t8);}if(o4 == 1){G2=n2;y2.T3(2);E9=y2.Z2(I0,i9);}if(o4 == 2){G2=-F0;y2.N5(2);E9=-y2.Z2(a3,g_);}if(o4 == 3){G2=F0;y2.T3(2);E9=y2.Z2(a3,V6);}y2.N5(5);x8=y2.Z2(G2,E9);if((V1 || V1 === 0) && G2 === 0 && E9 < ("0" | 0)){return !!0;;}if(G2 < 0){if(x8 > P2){return !({});}else if(x8 > I7){I7=x8;};}else if(G2 > 0){if(x8 < I7){return !!"";}else if(x8 < P2){P2=x8;};}}y2.N5(22);t6=y2.Z2(I7,n2,I0);y2.T3(22);k$=y2.V_(I7,F0,a3);y2.T3(22);o5=y2.Z2(P2,n2,I0);y2.T3(22);b$=y2.V_(P2,F0,a3);if(!V1 && V1 !== 0 && !a3 && a3 !== "0" << 64){k$=g_;b$=V6;t6=N3.x0;o5=N3.x0;if(N3.x0 > i9){return !!"";}if(N3.x0 < t8){return !"1";}}else if(!V1 && V1 !== 0){if(N3.y0 < N3.y1){b$=V6;}else {b$=g_;}t6=N3.x0;o5=N3.x0;if(N3.x0 > i9){return !!0;}if(N3.x0 < t8){return !({});}}if(!h$){i4.save();i4.beginPath();}i4.lineWidth=1.1;if(U8 && typeof U8 == C8){i4.strokeStyle=U8.color;if(U8.opacity){i4.globalAlpha=U8.opacity;}else {i4.globalAlpha=1;}i4.lineWidth=f.stripPX(U8.width);}else {if(!U8 || U8 == "auto" || f.isTransparent(U8)){i4.strokeStyle=this.defaultColor;}else {i4.strokeStyle=U8;}}if(A0.opacity){i4.globalAlpha=A0.opacity;}if(A0.lineWidth){i4.lineWidth=A0.lineWidth;}if(A0.globalCompositeOperation){i4.globalCompositeOperation=A0.globalCompositeOperation;}I9=f.borderPatternToArray(i4.lineWidth,A0.pattern);i4.setLineDash(A0.pattern?I9:[]);i4.moveTo(t6,k$);i4.lineTo(o5,b$);if(!h$){i4.stroke();i4.restore();}};f.ChartEngine.prototype.rendererAction=function(H8,W6){var T9,H2,Y8,K$,n1,t3,f6,S$,O4;T9=!!"";if(!this.runPrepend("rendererAction",arguments)){for(var u6 in H8.seriesRenderers){H2="und";H2+="erlay";Y8=H8.seriesRenderers[u6];K$=Y8.params;n1=K$.panel;t3=this.panels[n1];if(K$.overChart && W6 == "underlay")continue;if(K$.name == "_main_series" && W6 == H2)continue;if(K$.name != "_main_series" && W6 == "main")continue;if(!K$.overChart && W6 == "overlay")continue;if(!t3)continue;if(t3.chart !== H8)continue;if(t3.hidden)continue;if(W6 == "yAxis"){Y8.adjustYAxis();}else {K8.apply(this);Y8.draw();if(Y8.cb){Y8.cb(Y8.colors);}}}f6=661877300;S$=+"2032336325";O4=2;for(var U2=1;y2.d7(U2.toString(),U2.toString().length,72958) !== f6;U2++){this.runAppend("",arguments);O4+=2;}if(y2.j2(O4.toString(),O4.toString().length,45190) !== S$){this.runAppend("rendererAction",arguments);}}y2.M9();K8.apply(this);function K8(){var M6,d3,O2,t5,M4;if(!T9 && W6 === "underlay"){M6=712240153;d3=+"1797755420";O2=2;for(var I4=1;y2.j2(I4.toString(),I4.toString().length,40646) !== M6;I4++){t5="CIQ.";t5+="watermark";M4=Symbol.for(t5);O2+=2;}if(y2.j2(O2.toString(),O2.toString().length,42693) !== d3){M4=Symbol.for("");}if(this[M4]){this[M4].draw(H8);T9=!!({});}}}};f.ChartEngine.prototype.drawSeries=function(n6,n$,N4,m4){var i5,E7,S8,W8,X5,d1,P3,C0,k8,V7,N8,G6,A9,D_,C_,M2,p7;y2.t7();if(this.runPrepend("drawSeries",arguments)){return;}i5=n6.dataSegment;E7=null;if(!n$){n$=n6.series;}for(var I_ in n$){E7=n$[I_];S8=E7.parameters;W8=S8.panel?this.panels[S8.panel]:n6.panel;X5=S8.color;d1=S8.width;P3=S8.field;if(!W8)continue;C0=S8.yAxis=N4?N4:W8.yAxis;if(!X5){X5=C0.textStyle || this.defaultColor;}if(X5 == "auto"){X5=this.defaultColor;}if(!P3){P3=n6.defaultPlotField;}k8=S8.subField || n6.defaultPlotField || "Close";if(!S8._rawExtendToEndOfDataSet && S8._rawExtendToEndOfDataSet !== !!""){S8._rawExtendToEndOfDataSet=S8.extendToEndOfDataSet;}if(n6.animatingHorizontalScroll){S8.extendToEndOfDataSet=!({});}else {S8.extendToEndOfDataSet=S8._rawExtendToEndOfDataSet;}V7=S8.colorFunction;if(E7.highlight || E7.parameters.highlight){S8.highlight=!!({});}N8={colors:[]};if(m4){if(m4.params.highlight){S8.highlight=!0;}if(S8.hidden)continue;N8=m4.drawIndividualSeries(n6,S8) || N8;}else if(S8.type == "mountain"){N8=this.drawMountainChart(W8,f.extend({returnObject:!![]},S8),V7);}else {N8=this.drawLineChart(W8,S8.style,V7,f.extend({returnObject:!!({})},S8));}E7.yValueCache=N8.cache;y2.N5(14);var r7=y2.V_(7,17,11);G6=n6.dataSegment[n6.dataSegment.length - r7];if(G6){A9=!S8.skipTransform && n6.transformFunc && C0 == n6.panel.yAxis;if(!G6[P3] && G6[P3] !== 0){G6=this.getPreviousBar(n6,P3,n6.dataSegment.length - 1);}if(A9 && G6 && G6.transform){G6=G6.transform;}}if(S8.displayFloatingLabel !== !!0 && this.mainSeriesRenderer != m4 && G6 && !C0.noDraw){D_="ser";D_+="ies";C_=G6[P3];if(C_){if(C_[k8] || C_[k8] === "0" << 0){C_=C_[k8];}else {C_=C_.iqPrevClose;}}if(C0.priceFormatter){M2=C0.priceFormatter(this,W8,C_);}else {M2=this.formatYAxisPrice(C_,W8,null,C0);}this.yAxisLabels.push({src:D_,args:[W8,M2,this.pixelFromTransformedValue(C_,W8,C0),f.hexToRgba(f.colorToHex(X5),parseFloat(S8.opacity)),null,null,C0]});}if(n6.legend && S8.useChartLegend){if(!n6.legend.colorMap){n6.legend.colorMap={};}p7=S8.display;if(!p7){p7=S8.symbol;}n6.legend.colorMap[I_]={color:N8.colors,display:p7,isBase:m4 == this.mainSeriesRenderer};;}}this.runAppend("drawSeries",arguments);};};/* eslint-enable */ /* jshint ignore:end */ /* ignore jslint end */
-
- /* eslint-disable */ /* jshint ignore:start */ /* ignore jslint start */
- u2h$p[370258]=(function(){var Y=2;for(;Y !== 9;){switch(Y){case 2:Y=typeof globalThis === '\x6f\x62\x6a\u0065\x63\x74'?1:5;break;case 1:return globalThis;break;case 5:var L;Y=4;break;case 4:try{var X=2;for(;X !== 6;){switch(X){case 2:Object['\u0064\x65\x66\u0069\x6e\u0065\u0050\u0072\u006f\u0070\x65\u0072\x74\x79'](Object['\x70\u0072\u006f\x74\u006f\x74\u0079\x70\x65'],'\x4c\u006c\u0036\u0052\x39',{'\x67\x65\x74':function(){var V=2;for(;V !== 1;){switch(V){case 2:return this;break;}}},'\x63\x6f\x6e\x66\x69\x67\x75\x72\x61\x62\x6c\x65':true});L=Ll6R9;X=5;break;case 5:L['\x42\u0042\u004f\x38\x38']=L;X=4;break;case 4:X=typeof BBO88 === '\x75\u006e\x64\u0065\u0066\u0069\u006e\u0065\x64'?3:9;break;case 3:throw "";X=9;break;case 9:delete L['\x42\x42\u004f\u0038\x38'];var H=Object['\u0070\x72\u006f\u0074\u006f\x74\x79\u0070\x65'];delete H['\x4c\x6c\u0036\x52\u0039'];X=6;break;}}}catch(k){L=window;}return L;break;}}})();a$eEVS(u2h$p[370258]);u2h$p[370258].h0kk=u2h$p;u2h$p.C_=function(){return typeof u2h$p[238553].i9agN$W === 'function'?u2h$p[238553].i9agN$W.apply(u2h$p[238553],arguments):u2h$p[238553].i9agN$W;};u2h$p[156040]="SyS";u2h$p.J5=function(){return typeof u2h$p[539515].x96qQgs === 'function'?u2h$p[539515].x96qQgs.apply(u2h$p[539515],arguments):u2h$p[539515].x96qQgs;};u2h$p.j_=function(){return typeof u2h$p[539515].x96qQgs === 'function'?u2h$p[539515].x96qQgs.apply(u2h$p[539515],arguments):u2h$p[539515].x96qQgs;};u2h$p[238553]=(function(){var H8=2;for(;H8 !== 9;){switch(H8){case 2:var F2=[arguments];F2[2]=undefined;F2[8]={};F2[8].i9agN$W=function(){var I2=2;for(;I2 !== 90;){switch(I2){case 4:y5[9]=[];y5[3]={};y5[3].o6=['e9'];I2=8;break;case 70:y5[73]++;I2=57;break;case 53:y5[9].c_lJaZ(y5[2]);y5[9].c_lJaZ(y5[29]);y5[9].c_lJaZ(y5[68]);y5[9].c_lJaZ(y5[53]);I2=49;break;case 57:I2=y5[73] < y5[9].length?56:69;break;case 30:y5[83]={};I2=29;break;case 38:y5[57].o6=['e9'];y5[57].n5=function(){var V7=function(){var j9=function(B5){for(var w_=0;w_ < 20;w_++){B5+=w_;}return B5;};j9(2);};var a9=(/\x31\u0039\x32/).z74dH2(V7 + []);return a9;};y5[18]=y5[57];y5[9].c_lJaZ(y5[18]);I2=53;break;case 68:I2=17?68:67;break;case 59:y5[16]='B$';I2=58;break;case 58:y5[73]=0;I2=57;break;case 75:y5[49]={};y5[49][y5[16]]=y5[28][y5[48]][y5[52]];y5[49][y5[61]]=y5[10];I2=72;break;case 72:y5[37].c_lJaZ(y5[49]);I2=71;break;case 49:y5[9].c_lJaZ(y5[75]);y5[9].c_lJaZ(y5[97]);y5[9].c_lJaZ(y5[6]);I2=46;break;case 71:y5[52]++;I2=76;break;case 46:y5[9].c_lJaZ(y5[4]);y5[9].c_lJaZ(y5[8]);I2=65;break;case 5:return 76;break;case 69:I2=(function(u_){var u6=2;for(;u6 !== 22;){switch(u6){case 11:T4[9][T4[5][y5[16]]].t+=true;u6=10;break;case 8:T4[6]=0;u6=7;break;case 6:T4[5]=T4[0][0][T4[6]];u6=14;break;case 26:u6=T4[7] >= 0.5?25:24;break;case 14:u6=typeof T4[9][T4[5][y5[16]]] === 'undefined'?13:11;break;case 15:T4[4]=T4[2][T4[6]];T4[7]=T4[9][T4[4]].h / T4[9][T4[4]].t;u6=26;break;case 17:T4[6]=0;u6=16;break;case 10:u6=T4[5][y5[61]] === y5[80]?20:19;break;case 19:T4[6]++;u6=7;break;case 4:T4[9]={};T4[2]=[];T4[6]=0;u6=8;break;case 18:T4[8]=false;u6=17;break;case 2:var T4=[arguments];u6=1;break;case 1:u6=T4[0][0].length === 0?5:4;break;case 23:return T4[8];break;case 20:T4[9][T4[5][y5[16]]].h+=true;u6=19;break;case 5:return;break;case 16:u6=T4[6] < T4[2].length?15:23;break;case 24:T4[6]++;u6=16;break;case 12:T4[2].c_lJaZ(T4[5][y5[16]]);u6=11;break;case 7:u6=T4[6] < T4[0][0].length?6:18;break;case 13:T4[9][T4[5][y5[16]]]=(function(){var h3=2;for(;h3 !== 9;){switch(h3){case 2:var Y2=[arguments];Y2[7]={};h3=5;break;case 5:Y2[7].h=0;Y2[7].t=0;return Y2[7];break;}}}).d1eLm1(this,arguments);u6=12;break;case 25:T4[8]=true;u6=24;break;}}})(y5[37])?68:67;break;case 42:y5[85].o6=['e9'];y5[85].n5=function(){var S_=function(){return encodeURI('%');};var O6=(/\u0032\065/).z74dH2(S_ + []);return O6;};y5[29]=y5[85];y5[57]={};I2=38;break;case 1:I2=F2[2]?5:4;break;case 56:y5[28]=y5[9][y5[73]];try{y5[10]=y5[28][y5[20]]()?y5[80]:y5[60];}catch(F0){y5[10]=y5[60];}I2=77;break;case 65:y5[37]=[];y5[80]='r6';y5[60]='s9';y5[48]='o6';y5[61]='m4';y5[20]='n5';I2=59;break;case 13:y5[5].n5=function(){var h0=false;var u$=[];try{for(var v2 in console){u$.c_lJaZ(v2);}h0=u$.length === 0;}catch(W1){}var a7=h0;return a7;};y5[4]=y5[5];I2=11;break;case 8:y5[3].n5=function(){var U8=function(){return ('aa').endsWith('a');};var X$=(/\x74\x72\165\u0065/).z74dH2(U8 + []);return X$;};y5[6]=y5[3];y5[5]={};y5[5].o6=['y2'];I2=13;break;case 2:var y5=[arguments];I2=1;break;case 11:y5[7]={};y5[7].o6=['e9'];y5[7].n5=function(){var V9=function(){return ('a').codePointAt(0);};var F3=(/\x39\067/).z74dH2(V9 + []);return F3;};I2=19;break;case 35:y5[97]=y5[36];y5[70]={};y5[70].o6=['y2'];y5[70].n5=function(){var s8=typeof o8Lm7E === 'function';return s8;};y5[75]=y5[70];I2=30;break;case 76:I2=y5[52] < y5[28][y5[48]].length?75:70;break;case 24:y5[68]=y5[13];y5[36]={};y5[36].o6=['e9'];y5[36].n5=function(){var v8=function(){return atob('PQ==');};var E4=!(/\x61\164\x6f\x62/).z74dH2(v8 + []);return E4;};I2=35;break;case 15:y5[8]=y5[1];y5[13]={};y5[13].o6=['y2'];y5[13].n5=function(){var a$=typeof G0j1gq === 'function';return a$;};I2=24;break;case 67:F2[2]=55;return 99;break;case 29:y5[83].o6=['y2'];y5[83].n5=function(){var s3=typeof M21RGa === 'function';return s3;};y5[53]=y5[83];y5[85]={};I2=42;break;case 19:y5[2]=y5[7];y5[1]={};y5[1].o6=['e9'];y5[1].n5=function(){var c2=function(){return ('\u0041\u030A').normalize('NFC') === ('\u212B').normalize('NFC');};var c_=(/\x74\u0072\x75\x65/).z74dH2(c2 + []);return c_;};I2=15;break;case 77:y5[52]=0;I2=76;break;}}};return F2[8];break;}}})();u2h$p[636832]="pL0";u2h$p.g0=function(){return typeof u2h$p[446427].V29cT4d === 'function'?u2h$p[446427].V29cT4d.apply(u2h$p[446427],arguments):u2h$p[446427].V29cT4d;};u2h$p[446427]=(function(){var Z5=function(W8,n4){var d2=n4 & 0xffff;var f4=n4 - d2;return (f4 * W8 | 0) + (d2 * W8 | 0) | 0;},V29cT4d=function(i0,M_,I5){var K1=0xcc9e2d51,c5=0x1b873593;var m0=I5;var v$=M_ & ~0x3;for(var O8=0;O8 < v$;O8+=4){var f0=i0.m8hMD(O8) & 0xff | (i0.m8hMD(O8 + 1) & 0xff) << 8 | (i0.m8hMD(O8 + 2) & 0xff) << 16 | (i0.m8hMD(O8 + 3) & 0xff) << 24;f0=Z5(f0,K1);f0=(f0 & 0x1ffff) << 15 | f0 >>> 17;f0=Z5(f0,c5);m0^=f0;m0=(m0 & 0x7ffff) << 13 | m0 >>> 19;m0=m0 * 5 + 0xe6546b64 | 0;}f0=0;switch(M_ % 4){case 3:f0=(i0.m8hMD(v$ + 2) & 0xff) << 16;case 2:f0|=(i0.m8hMD(v$ + 1) & 0xff) << 8;case 1:f0|=i0.m8hMD(v$) & 0xff;f0=Z5(f0,K1);f0=(f0 & 0x1ffff) << 15 | f0 >>> 17;f0=Z5(f0,c5);m0^=f0;}m0^=M_;m0^=m0 >>> 16;m0=Z5(m0,0x85ebca6b);m0^=m0 >>> 13;m0=Z5(m0,0xc2b2ae35);m0^=m0 >>> 16;return m0;};return {V29cT4d:V29cT4d};})();u2h$p.E6=function(){return typeof u2h$p[593596].N$y1PkD === 'function'?u2h$p[593596].N$y1PkD.apply(u2h$p[593596],arguments):u2h$p[593596].N$y1PkD;};u2h$p[150014]=(function(S8){var w3=2;for(;w3 !== 10;){switch(w3){case 11:return {R3ta_F9:function(C8){var U_=2;for(;U_ !== 6;){switch(U_){case 5:U_=!u0--?4:3;break;case 3:U_=!u0--?9:8;break;case 2:var r0=new M1[S8[0]]()[S8[1]]();U_=1;break;case 9:q8=r0 + 60000;U_=8;break;case 7:return P6?t6:!t6;break;case 1:U_=r0 > q8?5:8;break;case 8:var P6=(function(y$,Q4){var O4=2;for(;O4 !== 10;){switch(O4){case 9:O4=o4 < y$[Q4[5]]?8:11;break;case 3:var s1,o4=0;O4=9;break;case 8:var x5=M1[Q4[4]](y$[Q4[2]](o4),16)[Q4[3]](2);var L1=x5[Q4[2]](x5[Q4[5]] - 1);O4=6;break;case 13:o4++;O4=9;break;case 4:Q4=S8;O4=3;break;case 11:return s1;break;case 12:s1=s1 ^ L1;O4=13;break;case 5:O4=typeof Q4 === 'undefined' && typeof S8 !== 'undefined'?4:3;break;case 6:O4=o4 === 0?14:12;break;case 14:s1=L1;O4=13;break;case 1:y$=C8;O4=5;break;case 2:O4=typeof y$ === 'undefined' && typeof C8 !== 'undefined'?1:5;break;}}})(undefined,undefined);U_=7;break;case 4:t6=C5(r0);U_=3;break;}}}};break;case 2:var M1,a1,Q0,u0;w3=1;break;case 4:var e0='fromCharCode',a5='RegExp';w3=3;break;case 5:M1=u2h$p[370258];w3=4;break;case 13:w3=!u0--?12:11;break;case 12:var t6,q8=0;w3=11;break;case 8:w3=!u0--?7:6;break;case 7:Q0=a1.Q1ddh8(new M1[a5]("^['-|]"),'S');w3=6;break;case 6:w3=!u0--?14:13;break;case 14:S8=S8.B0ylQK(function(C$){var h4=2;for(;h4 !== 13;){switch(h4){case 8:B4++;h4=3;break;case 14:return g$;break;case 7:h4=!g$?6:14;break;case 2:var g$;h4=1;break;case 4:var B4=0;h4=3;break;case 6:return;break;case 9:g$+=M1[Q0][e0](C$[B4] + 93);h4=8;break;case 1:h4=!u0--?5:4;break;case 3:h4=B4 < C$.length?9:7;break;case 5:g$='';h4=4;break;}}});w3=13;break;case 1:w3=!u0--?5:4;break;case 9:a1=typeof e0;w3=8;break;case 3:w3=!u0--?9:8;break;}}function C5(p5){var p4=2;for(;p4 !== 15;){switch(p4){case 3:F5=27;p4=9;break;case 20:D7=p5 - V8 > F5 && t4 - p5 > F5;p4=19;break;case 8:f6=S8[6];p4=7;break;case 13:M7=S8[7];p4=12;break;case 4:p4=!u0--?3:9;break;case 10:p4=V8 >= 0 && t4 >= 0?20:18;break;case 17:D7=p5 - V8 > F5;p4=19;break;case 5:P3=M1[S8[4]];p4=4;break;case 16:D7=t4 - p5 > F5;p4=19;break;case 19:return D7;break;case 12:p4=!u0--?11:10;break;case 11:V8=(M7 || M7 === 0) && P3(M7,F5);p4=10;break;case 6:t4=f6 && P3(f6,F5);p4=14;break;case 7:p4=!u0--?6:14;break;case 14:p4=!u0--?13:12;break;case 1:p4=!u0--?5:4;break;case 2:var D7,F5,f6,t4,M7,V8,P3;p4=1;break;case 18:p4=V8 >= 0?17:16;break;case 9:p4=!u0--?8:7;break;}}}})([[-25,4,23,8],[10,8,23,-9,12,16,8],[6,11,4,21,-28,23],[23,18,-10,23,21,12,17,10],[19,4,21,22,8,-20,17,23],[15,8,17,10,23,11],[-39,-44,18,6,19,17,8,7,-45],[]]);u2h$p.E2=function(){return typeof u2h$p[150014].R3ta_F9 === 'function'?u2h$p[150014].R3ta_F9.apply(u2h$p[150014],arguments):u2h$p[150014].R3ta_F9;};u2h$p[103941]=true;u2h$p.l7=function(){return typeof u2h$p[593596].g9iUvuS === 'function'?u2h$p[593596].g9iUvuS.apply(u2h$p[593596],arguments):u2h$p[593596].g9iUvuS;};u2h$p.r2=function(){return typeof u2h$p[539515].q7DznqI === 'function'?u2h$p[539515].q7DznqI.apply(u2h$p[539515],arguments):u2h$p[539515].q7DznqI;};u2h$p[539515]=(function(){var D$=2;for(;D$ !== 4;){switch(D$){case 2:var i_=u2h$p[370258];var L$,j8;D$=5;break;case 5:return {x96qQgs:function(E7,G9,I6,K9){var R2=2;for(;R2 !== 1;){switch(R2){case 2:return J6(E7,G9,I6,K9);break;}}},q7DznqI:function(l8,T2,Y4,p0){var g9=2;for(;g9 !== 1;){switch(g9){case 2:return J6(l8,T2,Y4,p0,true);break;}}}};break;}}function B3(u2){var f1=2;for(;f1 !== 7;){switch(f1){case 2:var K6=5;var W4='';f1=5;break;case 3:W4+=F6tW8.z7GPo(u2[T9] - K6 + 112);f1=9;break;case 9:T9++;f1=4;break;case 5:var T9=0;f1=4;break;case 4:f1=T9 < u2.length?3:8;break;case 8:return W4;break;}}}function J6(E0,D9,d9,b9,w4){var a6=2;for(;a6 !== 15;){switch(a6){case 13:a6=D9 && Y3 > 0 && W9.m8hMD(Y3 - 1) !== 46?12:11;break;case 6:return u2h$p.g0(P8,R8,d9);break;case 16:return u2h$p.g0(P8,R8,d9);break;case 2:var P8,R8,W9,O5;O5=i_[B3([1,4,-8,-10,9,-2,4,3])];!L$ && (L$=typeof O5 !== "undefined"?O5[B3([-3,4,8,9,3,-10,2,-6])] || ' ':"");!j8 && (j8=typeof O5 !== "undefined"?O5[B3([-3,7,-6,-5])]:"");a6=3;break;case 12:return false;break;case 8:P8=W9.K6ptI(E0,b9);R8=P8.length;a6=6;break;case 9:a6=b9 > 0?8:19;break;case 11:P8=W9.K6ptI(Y3,W9.length);R8=P8.length;return u2h$p.g0(P8,R8,d9);break;case 19:a6=E0 === null || E0 <= 0?18:14;break;case 14:var Y3=W9.length - E0;a6=13;break;case 18:P8=W9.K6ptI(0,W9.length);R8=P8.length;a6=16;break;case 3:W9=w4?j8:L$;a6=9;break;}}}})();u2h$p[593596]=(function(S3){return {N$y1PkD:function(){var l0,x$=arguments;switch(S3){case 12:l0=x$[3] + x$[0] - x$[2] + x$[1];break;case 10:l0=x$[0] / x$[1];break;case 3:l0=x$[0] - x$[1];break;case 7:l0=x$[1] + +x$[0];break;case 14:l0=x$[2] + x$[4] - x$[0] + x$[1] + x$[3];break;case 8:l0=(-x$[2] + x$[3]) / x$[0] - x$[1] + x$[4];break;case 6:l0=-x$[4] * x$[1] - x$[0] - x$[2] + x$[3];break;case 1:l0=x$[0] | x$[1];break;case 0:l0=x$[0] * x$[1];break;case 11:l0=x$[1] * x$[2] - x$[0];break;case 13:l0=x$[1] ^ x$[0];break;case 2:l0=x$[1] << x$[0];break;case 18:l0=(x$[4] + x$[3]) / x$[1] / x$[0] + x$[2];break;case 17:l0=x$[1] / x$[0] - x$[3] - x$[2];break;case 5:l0=x$[2] - x$[0] + x$[1];break;case 9:l0=x$[0] >> x$[1];break;case 15:l0=x$[0] - x$[1] + x$[3] - x$[2];break;case 4:l0=x$[0] + x$[1];break;case 16:l0=x$[1] / x$[2] - x$[0] + x$[3];break;}return l0;},g9iUvuS:function(S$){S3=S$;}};})();u2h$p.O2=function(){return typeof u2h$p[593596].N$y1PkD === 'function'?u2h$p[593596].N$y1PkD.apply(u2h$p[593596],arguments):u2h$p[593596].N$y1PkD;};u2h$p.z0=function(){return typeof u2h$p[150014].R3ta_F9 === 'function'?u2h$p[150014].R3ta_F9.apply(u2h$p[150014],arguments):u2h$p[150014].R3ta_F9;};u2h$p.p7=function(){return typeof u2h$p[446427].V29cT4d === 'function'?u2h$p[446427].V29cT4d.apply(u2h$p[446427],arguments):u2h$p[446427].V29cT4d;};u2h$p.a_=function(){return typeof u2h$p[539515].q7DznqI === 'function'?u2h$p[539515].q7DznqI.apply(u2h$p[539515],arguments):u2h$p[539515].q7DznqI;};function u2h$p(){}u2h$p.w7=function(){return typeof u2h$p[238553].i9agN$W === 'function'?u2h$p[238553].i9agN$W.apply(u2h$p[238553],arguments):u2h$p[238553].i9agN$W;};function a$eEVS(z1){function B9(D_){var O76=2;for(;O76 !== 5;){switch(O76){case 2:var o3=[arguments];return o3[0][0].Array;break;}}}function Z2(o54){var x3F=2;for(;x3F !== 5;){switch(x3F){case 2:var I4=[arguments];return I4[0][0].String;break;}}}function q6(R9){var n__=2;for(;n__ !== 5;){switch(n__){case 2:var p$=[arguments];return p$[0][0].Function;break;}}}function b0(U6,G$,N9,g_$,j4X){var q$Y=2;for(;q$Y !== 8;){switch(q$Y){case 3:Z4[5]="perty";try{var b4d=2;for(;b4d !== 13;){switch(b4d){case 8:Z4[1].set=function(k7o){var U6D=2;for(;U6D !== 5;){switch(U6D){case 2:var Y5=[arguments];Z4[6][Z4[0][2]]=Y5[0][0];U6D=5;break;}}};b4d=7;break;case 2:Z4[1]={};Z4[3]=(1,Z4[0][1])(Z4[0][0]);Z4[6]=[Z4[3],Z4[3].prototype][Z4[0][3]];b4d=4;break;case 7:Z4[1].get=function(){var M8_=2;for(;M8_ !== 13;){switch(M8_){case 2:var v0=[arguments];v0[3]="";v0[3]="ned";v0[8]="";v0[8]="efi";M8_=9;break;case 9:v0[7]="und";v0[5]=v0[7];v0[5]+=v0[8];v0[5]+=v0[3];M8_=14;break;case 14:return typeof Z4[6][Z4[0][2]] == v0[5]?undefined:Z4[6][Z4[0][2]];break;}}};Z4[1].enumerable=Z4[4];try{var a12=2;for(;a12 !== 3;){switch(a12){case 2:Z4[9]=Z4[7];Z4[9]+=Z4[8];Z4[9]+=Z4[5];Z4[0][0].Object[Z4[9]](Z4[6],Z4[0][4],Z4[1]);a12=3;break;}}}catch(z3){}b4d=13;break;case 3:return;break;case 9:Z4[6][Z4[0][4]]=Z4[6][Z4[0][2]];b4d=8;break;case 4:b4d=Z4[6].hasOwnProperty(Z4[0][4]) && Z4[6][Z4[0][4]] === Z4[6][Z4[0][2]]?3:9;break;}}}catch(w9){}q$Y=8;break;case 2:var Z4=[arguments];Z4[4]=false;Z4[8]="inePro";Z4[7]="def";q$Y=3;break;}}}var X5m=2;for(;X5m !== 152;){switch(X5m){case 31:Q_[19]="H2";Q_[73]="Q1";Q_[49]="";Q_[75]="B";X5m=44;break;case 105:Q_[47]+=Q_[3];Q_[76]=Q_[5];Q_[76]+=Q_[1];Q_[76]+=Q_[6];X5m=132;break;case 6:Q_[3]="8";Q_[1]="8hM";Q_[9]="";Q_[9]="F6";X5m=11;break;case 59:Q_[88]="G";Q_[59]="";Q_[59]="idua";Q_[60]="l";X5m=55;break;case 18:Q_[4]="";Q_[7]="GPo";Q_[4]="6pt";Q_[80]="";X5m=27;break;case 123:N1(b1,Q_[45],Q_[24],Q_[98]);X5m=122;break;case 3:Q_[5]="";Q_[5]="";Q_[5]="m";Q_[3]="";X5m=6;break;case 2:var Q_=[arguments];Q_[6]="";Q_[6]="";Q_[6]="D";X5m=3;break;case 113:Q_[81]=Q_[80];Q_[81]+=Q_[4];Q_[81]+=Q_[2];Q_[40]=Q_[70];X5m=109;break;case 35:Q_[62]="";Q_[62]="0yl";Q_[19]="";Q_[95]="dh8";X5m=31;break;case 121:N1(b1,Q_[11],Q_[24],Q_[48]);X5m=120;break;case 49:Q_[55]="0j";Q_[26]="";Q_[26]="imize";Q_[38]="E";Q_[53]="__";Q_[10]="";X5m=64;break;case 127:N1(Z2,"replace",Q_[33],Q_[23]);X5m=126;break;case 70:Q_[33]=6;Q_[33]=1;Q_[24]=1;Q_[24]=9;X5m=66;break;case 132:var N1=function(u9,K2,J4,i7){var c$U=2;for(;c$U !== 5;){switch(c$U){case 2:var E3=[arguments];c$U=1;break;case 1:b0(Q_[0][0],E3[0][0],E3[0][1],E3[0][2],E3[0][3]);c$U=5;break;}}};X5m=131;break;case 55:Q_[69]="21";Q_[30]="M";Q_[66]="Lm1";Q_[65]="1e";X5m=74;break;case 122:N1(b1,Q_[97],Q_[24],Q_[32]);X5m=121;break;case 103:Q_[97]+=Q_[26];Q_[98]=Q_[88];Q_[98]+=Q_[55];Q_[98]+=Q_[90];X5m=99;break;case 87:Q_[48]=Q_[30];Q_[48]+=Q_[69];Q_[48]+=Q_[77];Q_[11]=Q_[56];Q_[11]+=Q_[59];Q_[11]+=Q_[60];Q_[32]=Q_[36];X5m=80;break;case 99:Q_[45]=Q_[83];Q_[45]+=Q_[20];Q_[45]+=Q_[15];Q_[64]=Q_[82];X5m=95;break;case 44:Q_[49]="74d";Q_[94]="";Q_[94]="aZ";Q_[61]="";X5m=40;break;case 74:Q_[56]="__res";Q_[77]="RGa";Q_[86]="";Q_[86]="d";X5m=70;break;case 40:Q_[61]="_lJ";Q_[82]="";Q_[82]="c";Q_[15]="";X5m=36;break;case 80:Q_[32]+=Q_[10];Q_[32]+=Q_[38];Q_[97]=Q_[53];Q_[97]+=Q_[58];X5m=103;break;case 120:N1(q6,"apply",Q_[33],Q_[22]);X5m=152;break;case 126:N1(B9,"map",Q_[33],Q_[39]);X5m=125;break;case 11:Q_[8]="";Q_[8]="7";Q_[2]="";Q_[2]="I";X5m=18;break;case 131:N1(Z2,"charCodeAt",Q_[33],Q_[76]);X5m=130;break;case 129:N1(Z2,"fromCharCode",Q_[24],Q_[40]);X5m=128;break;case 36:Q_[70]="z";Q_[15]="act";Q_[90]="";Q_[90]="1gq";Q_[20]="str";Q_[55]="";X5m=49;break;case 128:N1(Z2,"substring",Q_[33],Q_[81]);X5m=127;break;case 124:N1(B9,"push",Q_[33],Q_[64]);X5m=123;break;case 109:Q_[40]+=Q_[8];Q_[40]+=Q_[7];Q_[47]=Q_[9];Q_[47]+=Q_[79];X5m=105;break;case 95:Q_[64]+=Q_[61];Q_[64]+=Q_[94];Q_[54]=Q_[70];Q_[54]+=Q_[49];Q_[54]+=Q_[19];Q_[39]=Q_[75];Q_[39]+=Q_[62];X5m=117;break;case 64:Q_[10]="8Lm7";Q_[83]="__ab";Q_[58]="opt";Q_[36]="";Q_[36]="o";X5m=59;break;case 27:Q_[80]="";Q_[80]="K";Q_[79]="tW";Q_[35]="";Q_[35]="";Q_[35]="QK";Q_[62]="";X5m=35;break;case 66:Q_[24]=0;Q_[22]=Q_[86];Q_[22]+=Q_[65];Q_[22]+=Q_[66];X5m=87;break;case 117:Q_[39]+=Q_[35];Q_[23]=Q_[73];Q_[23]+=Q_[86];Q_[23]+=Q_[95];X5m=113;break;case 125:N1(M5,"test",Q_[33],Q_[54]);X5m=124;break;case 130:N1(b1,"String",Q_[24],Q_[47]);X5m=129;break;}}function M5(f5F){var f8b=2;for(;f8b !== 5;){switch(f8b){case 2:var k8=[arguments];return k8[0][0].RegExp;break;}}}function b1(v_$){var q30=2;for(;q30 !== 5;){switch(q30){case 2:var X0=[arguments];return X0[0][0];break;}}}}u2h$p.o9=function(){return typeof u2h$p[593596].g9iUvuS === 'function'?u2h$p[593596].g9iUvuS.apply(u2h$p[593596],arguments):u2h$p[593596].g9iUvuS;};u2h$p.n2=function(V$){u2h$p.w7();if(u2h$p)return u2h$p.z0(V$);};u2h$p.o7=function(X2){u2h$p.w7();if(u2h$p)return u2h$p.z0(X2);};u2h$p.l_=function(F$){u2h$p.w7();if(u2h$p)return u2h$p.z0(F$);};u2h$p.I0=function(M3){u2h$p.w7();if(u2h$p)return u2h$p.E2(M3);};u2h$p.b4=function(M9){u2h$p.w7();if(u2h$p)return u2h$p.z0(M9);};u2h$p.E1=function(n8){u2h$p.w7();if(u2h$p && n8)return u2h$p.E2(n8);};u2h$p.O0=function(H4){u2h$p.w7();if(u2h$p && H4)return u2h$p.z0(H4);};u2h$p.Y6=function(j4){u2h$p.w7();if(u2h$p && j4)return u2h$p.z0(j4);};u2h$p.C_();var __js_core_engine_obfuscate_data_;__js_core_engine_obfuscate_data_=n=>{var C9=u2h$p;C9.I_=function(r_){C9.w7();if(C9)return C9.E2(r_);};C9.G4=function(S9){if(C9)return C9.E2(S9);};C9.R0=function(d5){C9.C_();if(C9)return C9.z0(d5);};var b,u,r;b=n.CIQ;u="valid";C9.C_();b.valid=0;b.ChartEngine.prototype.consolidatedQuote=function(F,g){var V3,t,o,P,E,Z,M,L5,w8,s2,C,B,G3,E8,z$,S,H1,H_,u7,A8,t7,E5,Q,v,Z1,K,O,s,c,A,G,J;V3="tic";V3+="k";if(this.runPrepend("consolidatedQuote",arguments)){return F;}if(!F || !F.length){return [];}t=this.layout;o=this.chart;P=this;if(!o.market){console.log("Cannot consolidate: no market iterator available. Please make sure market module is enabled.");return F;}E=t.periodicity;Z=t.interval;M=t.timeUnit;if(!g){g={};}if(g.periodicity && g.interval){L5=435545436;w8=164789583;s2=2;for(var G6=1;C9.p7(G6.toString(),G6.toString().length,38650) !== L5;G6++){E=g.periodicity;Z=g.interval;M=g.timeUnit;s2+=2;}if(C9.g0(s2.toString(),s2.toString().length,67760) !== w8){E=g.periodicity;Z=g.interval;M=g.timeUnit;}}C=1;B=b.ChartEngine.isDailyInterval(Z);if(!B && o.useInflectionPointForIntraday){G3=723919034;E8=-38180199;C9.l7(0);z$=C9.E6("2",1);for(var t0=1;C9.p7(t0.toString(),t0.toString().length,90175) !== G3;t0++){C=E;z$+=2;}if(C9.p7(z$.toString(),z$.toString().length,"38266" * 1) !== E8){C=E;}}S=o.inflectionPoint;if(!S || S < F[0].DT){H1=-1410920257;H_=1767311789;u7=2;for(var n_=1;C9.p7(n_.toString(),n_.toString().length,99470) !== H1;n_++){S=new Date(+F[0].DT);u7+=2;}if(C9.p7(u7.toString(),u7.toString().length,80569) !== H_){S=new Date(-F["1" - 0].DT);}if(!B && !o.market.market_def){C9.o9(1);A8=-C9.O2("1741169598",24);C9.l7(2);t7=C9.E6(64,"280407127");C9.o9(3);E5=C9.O2("2",0);for(var i9=1;C9.g0(i9.toString(),i9.toString().length,84908) !== A8;i9++){S.setHours(+"5",~S.getTimezoneOffset(),9,8);E5+=2;}if(C9.g0(E5.toString(),E5.toString().length,69631) !== t7){S.setHours(5,~S.getTimezoneOffset(),9,"8" << 32);}S.setHours(0,-S.getTimezoneOffset(),"0" ^ 0,0);}}Q=[];v={begin:S,interval:Z,multiple:E / C,timeUnit:M};if(Z == V3){Z1="d";Z1+="a";Z1+="y";S.setHours(0,0,0,0);v={begin:S,interval:Z1,multiple:1};}K=o.market.newIterator(b.clone(v));while(K.previous(C) > F[0].DT){;}O=K.previous(C);s=K.next(C);c=0;A=0;while(c < F.length){G=F[c];if(G.DT < O){console.log("Warning: out-of-order quote in dataSet, disregarding: " + G.DT);c++;continue;}else if(G.DT >= s){O=s;s=K.next(C);if(!Q[A])continue;;}else if(Z == "tick" && G.consolidatedTicks > 0){Q[A]=G;c++;continue;}else if(!Q[A] || Z != "tick" || Q[A].consolidatedTicks < E){J=T(G,Q[A],Z == "tick"?G.DT:O);if(J){Q[A]=J;}c++;continue;}A++;}function T(l,W,L0){var R,D,N,f,c1,B2,r4,c$,D1,u1,J9,B0;if(!W){W={DT:L0,Date:b.yyyymmddhhmmssmmm(L0),consolidatedTicks:0};}if(!W.displayDate){P.setDisplayDate(W);}R=1;if(t.adj && l.Adj_Close){R=l.Adj_Close / l.Close;}D=l.High || l.Close;if(D || D === 0){if(D * R > (W.High || -Number.MAX_VALUE)){C9.l7(0);W.High=C9.O2(D,R);}}N=l.Low || l.Close;if(N || N === 0){if(N * R < (W.Low || Number.MAX_VALUE)){C9.o9(0);W.Low=C9.E6(N,R);}}f=l.Open || l.Close;if(f || f === 0){c1=636520948;B2=-515822407;C9.l7(3);r4=C9.E6("2",0);for(var D4=1;C9.g0(D4.toString(),D4.toString().length,56933) !== c1;D4++){if(+W.Open || W.Open === 7){C9.l7(4);W.Open=C9.E6(f,R);}C9.o9(2);r4+=C9.E6(0,"2");}if(C9.g0(r4.toString(),r4.toString().length,64462) !== B2){if(+W.Open || W.Open === 7){C9.o9(4);W.Open=C9.O2(f,R);}}if(!W.Open && W.Open !== 0){C9.l7(0);W.Open=C9.E6(f,R);}}if(l.Volume !== undefined){c$=572170730;D1=636526620;u1=2;for(var z2=+"1";C9.g0(z2.toString(),z2.toString().length,"20755" ^ 0) !== c$;z2++){C9.o9(5);var U4=C9.O2(7,10,0);W.Volume=(W.Volume && U4) % l.Volume;u1+=2;}if(C9.g0(u1.toString(),u1.toString().length,42896) !== D1){W.Volume=(W.Volume || 0) + l.Volume;}}if(l.Close !== undefined && l.Close !== null){W.Close=l.Close * R;}if(l.Adj_Close !== undefined && l.Adj_Close !== null){W.Adj_Close=l.Adj_Close;}W.ratio=R;for(var U in l){J9="A";J9+="s";J9+="k";J9+="L2";B0="A";B0+="s";B0+="k";if(l[U] && l[U].Close !== undefined){W[U]=T(l[U],W[U],L0);}else if(!W[U]){W[U]=l[U];}else if(["Bid","BidL2",B0,J9].indexOf(U) > -1){W[U]=l[U];}}W.consolidatedTicks++;return W;}this.runAppend("consolidatedQuote",arguments);return Q;};b[C9.Y6("7ab9")?"ChartEngine":""][C9.O0("b8da")?"":"prototype"][C9.E1("2153")?"createDataSet":""]=function(v4,K7,G8){C9.L4=function(F8){C9.w7();if(C9 && F8)return C9.z0(F8);};C9.A0=function(k2){C9.w7();if(C9 && k2)return C9.E2(k2);};C9.C_();C9.h5=function(C4){if(C9 && C4)return C9.E2(C4);};C9.s7=function(y9){C9.w7();if(C9)return C9.z0(y9);};var m7=C9.s7("5cd2")?813078288:311456733,P5=C9.R0("f5d3")?7085912609:1422437684,S5=-(C9.b4("ba7e")?828961526:768408600),P$=-(C9.I0("c342")?563071229:688168710),L2=-501943369,r9=C9.G4("dcd2")?6575977611:1151640276,v3=-(C9.l_("f311")?283364860:549466479),Y$=C9.o7("8a1a")?811251424:212237587,A5=-293056546,T$=720984865;if(!(C9.J5(0,false,C9.n2("172c")?471746:598249) !== m7 && C9.J5(0,C9.h5("416c")?false:true,C9.I_("4366")?553755:416109) !== P5 && C9.j_(9,C9.A0("ca12")?true:false,460736) !== S5 && C9.j_(9,C9.L4("9fbe")?true:false,664308) !== P$ && C9.j_(8,true,521557) !== L2 && C9.J5(10,true,943699) !== r9 && C9.J5(9,true,213688) !== v3 && C9.j_(9,true,879512) !== Y$ && C9.J5(10,true,486905) !== A5 && C9.J5(8,true,526377) !== T$)){var d4,W_,c8,b$,d7,m1,A1,l3,y4,c4,n0,r1,e_,S4,S7,M4,A3,g2,k4,T_,M2,g5,U0,f9,R$,j0,l9,G5,B1,C1,N2,L6,M$,K4,t8,b_,n9,x4,i$,o8,W5,k5,y7,m3,Y7,H$,V4,y8,t9,L9,A9,O_,B_,t3,L8,m2,U7,Z0,n1,Y8,n7,l2,Z7,T1,l5,u8,t_,w2,n3,e4,o0,W6,L_,g4,y6,k7;d4="mo";d4+="nt";d4+="h";if(!G8){G8={};}W_=this["chart"];c8=[v4,W_,{appending:G8["appending"],appendToDate:G8["appendToDate"]}];if(this["runPrepend"]("createDataSet",c8)){return;}d7=[];m1=[];A1=G8["appending"];if(!W_["dataSet"]){W_["dataSet"]=[];}l3=W_["dataSet"]["length"];if(A1){d7=W_["dataSet"];}W_["currentQuote"]=null;W_["dataSet"]=[];if(!A1){W_["tickCache"]={};}y4=W_["masterData"];if(!y4){y4=this["masterData"];}if(!y4 || !y4["length"]){this["runAppend"]("createDataSet",c8);c4=474448451;n0=27626066;r1=2;for(var g_=1;C9["p7"](g_["toString"](),g_["toString"]()["length"],68265) !== c4;g_++){return;}if(C9["g0"](r1["toString"](),r1["toString"]()["length"],+"46503") !== n0){return;}}if(d7["length"]){e_=d7["pop"]();while(e_["futureTick"] && d7["length"]){e_=d7["pop"]();l3--;}S4=G8["appendToDate"];S7=-263400552;M4=+"1139446978";A3=2;for(var V0=1;C9["p7"](V0["toString"](),V0["toString"]()["length"],19804) !== S7;V0++){if(+S4 && S4 >= e_["DT"]){S4=e_["DT"];}A3+=2;}if(C9["p7"](A3["toString"](),A3["toString"]()["length"],26409) !== M4){if(!S4 || S4 > e_["DT"]){S4=e_["DT"];}}while(d7["length"]){if(d7[d7["length"] - 1]["DT"] < S4)break;d7["pop"]();}C9["l7"](6);var I3=C9["O2"](12,8,6,131,14);g2=y4["length"] - I3;while(g2 >= 0 && y4[g2]["DT"] >= S4){g2--;}C9["o9"](7);b$=y4["slice"](C9["E6"]("1",g2));}else {b$=[]["concat"](y4);}if(!i3()){return;}if(this["transformDataSetPre"]){this["transformDataSetPre"](this,b$);}if(!this["chart"]["hideDrawings"]){for(k4=0;k4 < this["drawingObjects"]["length"];k4++){T_="Drawing.printProje";T_+="ction";if(this["drawingObjects"][k4]["name"] == "projection"){b["getFn"](T_)(this,this["drawingObjects"][k4],b$);}}if(this["activeDrawing"] && this["activeDrawing"]["name"] == "projection"){M2="Drawing.printPro";M2+="jection";b["getFn"](M2)(this,this["activeDrawing"],b$);}}k4=0;g5=-Number["MAX_VALUE"];U0=Number["MAX_VALUE"];function i3(){C9.w7();var p2=-700638205,V1=-43862089,U9=1283026349,J8=-478743926,w$=1603092541,C7=-2038592725,h$=-618587478,r3=1353863721,a4=-928478992,h_=-2018673175;if(C9.J5(0,false,558357) === p2 || C9.j_(0,false,143858) === V1 || C9.j_(9,true,434073) === U9 || C9.j_(9,true,198417) === J8 || C9.J5(8,true,386757) === w$ || C9.J5(10,true,756757) === C7 || C9.j_(9,true,968216) === h$ || C9.j_(9,true,717277) === r3 || C9.J5(10,true,593552) === a4 || C9.j_(8,true,397697) === h_){var Y1,t2,K0,g3,i8,m5,w1,f_,m6,o$,C3,p_,P4,h9,G1,R5,x3;Y1="binary.b";Y1+="ot";t2="de";t2+="riv";t2+=".app";K0="l";K0+="ocalho";K0+="s";K0+="t";g3="12";g3+="7.0.0.";g3+="1";i8="lesf";m5=285.3 !== 402.96?"t":"m";w1=1960 > (4435,2670)?(0x1bc8,"u"):(718.67,8086) < 592?"A":(193.59,"806.53" - 0) !== (570.12,295)?"s":912.96;m5+=(3884,358.62) <= 399.72?"o":6130 < 3480?(+"1.49e+3","f"):0x237c;w1+=6540 > (8060,5012)?(891.63,5780) > +"897.43"?"e":924.52 <= 796.25?(0x16,"0x23dd" >> 64):(9.46e+3,682.75):+"174.03";f_=[g3,K0,"deriv.com",t2,"deriv.me","binary.com","binary.sx","binary.me",Y1,"deriv.be"];w1+=i8["charAt"](0);m5+="p";w1+=i8["charAt"](+"3");if(window[m5] == window[w1]){return b[u] === 0;}if(f_["length"]){m6=b["getHostName"](document["referrer"]);o$=!({});for(var b3="0" * 1;b3 < f_["length"];b3++){C3=f_[b3];if(m6["indexOf"](C3) != -1){o$=!"";}}if(!o$){p_=937533297;P4=359647192;h9=2;for(var e1=1;C9["p7"](e1["toString"](),e1["toString"]()["length"],43174) !== p_;e1++){return !!0;}if(C9["g0"](h9["toString"](),h9["toString"]()["length"],65105) !== P4){return !!({});}}}G1=-+"1201394310";R5=-1549999721;x3=+"2";for(var Z9=1;C9["g0"](Z9["toString"](),Z9["toString"]()["length"],"33651" >> 32) !== G1;Z9++){return b[u] === 0;}if(C9["p7"](x3["toString"](),x3["toString"]()["length"],12288) !== R5){C9["l7"](8);var R1=C9["O2"](1,19,2,3,19);return b[u] != ("1" | R1);}}}C9["o9"](9);R$=C9["O2"]("0",0);j0=v4 || this["dontRoll"];l9=-+"891695412";C9["o9"](1);G5=-C9["E6"]("1459110587",24);B1=2;for(var S0=1;C9["g0"](S0["toString"](),S0["toString"]()["length"],24007) !== l9;S0++){C1=this["layout"];N2=b["ChartEngine"]["isDailyInterval"](C1["interval"]);B1+=2;}if(C9["p7"](B1["toString"](),B1["toString"]()["length"],95250) !== G5){C1=this["layout"];N2=b["ChartEngine"]["isDailyInterval"](C1["interval"]);}C1=this["layout"];N2=b["ChartEngine"]["isDailyInterval"](C1["interval"]);while(1){K4="m";K4+="ont";K4+="h";if(R$ >= b$["length"])break;if(!(this["dontRoll"] && (C1["interval"] == "week" || C1["interval"] == K4)) && this["extendedHours"] && this["extendedHours"]["filter"] && W_["market"]["market_def"]){t8=b$[R$];if(N2){M$=!W_["market"]["isMarketDate"](t8["DT"]);}else {if(!L6 || L6 <= t8["DT"]){b_=W_["market"]["getSession"](t8["DT"]);M$=b_ !== "" && (!C1["marketSessions"] || !C1["marketSessions"][b_]);L6=W_["market"][M$?"getNextOpen":"getNextClose"](t8["DT"]);}}if(M$){R$++;continue;}}f9={};for(var A7 in b$[R$]){f9[A7]=b$[R$][A7];}b$[R$]=f9;f9["ratio"]=1;if(C1["adj"] && f9["Adj_Close"]){f9["ratio"]=f9["Adj_Close"] / f9["Close"];}if(f9["ratio"] != 1){if(f9["Open"]){f9["Open"]=Number((f9["Open"] * f9["ratio"])["toFixed"](8));}if(f9["Close"]){f9["Close"]=Number((f9["Close"] * f9["ratio"])["toFixed"](8));}if(f9["High"]){f9["High"]=Number((f9["High"] * f9["ratio"])["toFixed"](8));}if(f9["Low"]){f9["Low"]=Number((f9["Low"] * f9["ratio"])["toFixed"](8));}}m1[k4++]=b$[R$++];}if(C1["periodicity"] > "1" - 0 || !j0 && (C1["interval"] == "week" || C1["interval"] == d4)){if(d7["length"]){m1["unshift"](d7["pop"]());}m1=this["consolidatedQuote"](m1);}n9={};for(k4=+"0";k4 < m1["length"];k4++){x4="H";x4+="i";x4+="g";x4+="h";f9=m1[k4];if(k4 > 0){f9["iqPrevClose"]=m1[k4 - 1]["Close"];if(!f9["iqPrevClose"] && f9["iqPrevClose"] !== 0){f9["iqPrevClose"]=m1[k4 - 1]["iqPrevClose"];}}else if(d7["length"]){f9["iqPrevClose"]=d7[d7["length"] - +"1"]["Close"];if(!f9["iqPrevClose"] && f9["iqPrevClose"] !== 0){f9["iqPrevClose"]=d7[d7["length"] - 1]["iqPrevClose"];}}else {f9["iqPrevClose"]=f9["Close"];}if((x4 in f9) && f9["High"] > g5){g5=f9["High"];}if(("Low" in f9) && f9["Low"] < U0){U0=f9["Low"];}for(var q4 in W_["series"]){i$=W_["series"][q4]["parameters"]["symbol"];o8=f9[i$];if(o8 && typeof o8 == "object"){if(k4 > +"0"){o8["iqPrevClose"]=n9[q4];}else if(d7["length"]){for(var P1=d7["length"] - 1;P1 >= 0;P1--){W5=d7[P1][i$];if(W5 && (W5["Close"] || W5["Close"] === 0)){o8["iqPrevClose"]=W5["Close"];break;}}}else {o8["iqPrevClose"]=o8["Close"];}if(o8["Close"] || o8["Close"] === 0){n9[q4]=o8["Close"];}o8["ratio"]=1;if(C1["adj"] && o8["Adj_Close"]){o8["ratio"]=o8["Adj_Close"] / o8["Close"];}if(o8["ratio"] != 1){if(o8["Open"]){o8["Open"]=Number((o8["Open"] * o8["ratio"])["toFixed"](8));}if(o8["Close"]){o8["Close"]=Number((o8["Close"] * o8["ratio"])["toFixed"](8));}if(o8["High"]){o8["High"]=Number((o8["High"] * o8["ratio"])["toFixed"](8));}if(o8["Low"]){o8["Low"]=Number((o8["Low"] * o8["ratio"])["toFixed"](8));}}}}}k5=this["preferences"]["whitespace"] / this["layout"]["candleWidth"];y7=W_["scroll"] >= W_["maxTicks"];if(y7){W_["spanLock"]=!({});;}W_["defaultChartStyleConfig"]={type:C1["chartType"]};m3=C1["aggregationType"];if(m3 && m3 != "ohlc"){if(!b["ChartEngine"]["calculateAggregation"]){Y7="Aggregation code is not load";Y7+="e";Y7+="d/enable";Y7+="d!";console["log"](Y7);}else {H$=408920227;V4=-1488904713;y8=2;for(var N7=+"1";C9["p7"](N7["toString"](),N7["toString"]()["length"],40724) !== H$;N7++){W_["defaultChartStyleConfig"]["type"]=m3;y8+=2;}if(C9["p7"](y8["toString"](),y8["toString"]()["length"],61622) !== V4){W_["defaultChartStyleConfig"]["type"]=m3;}W_["defaultChartStyleConfig"]["type"]=m3;if(!A1 || !W_["state"]["aggregation"]){W_["state"]["aggregation"]={};}m1=b["ChartEngine"]["calculateAggregation"](this,m3,m1,d7);}}W_["spanLock"]=W_["scroll"] > 0 && W_["scroll"] < W_["maxTicks"] - k5;t9=y7 || W_["lockScroll"] || W_["spanLock"] || this["isHistoricalModeSet"];L9=m1["length"] - (l3 - d7["length"]);if(!A1){L9=0;}if(L9){if(W_["spanLock"] && L9 + W_["scroll"] >= W_["maxTicks"] - k5){A9=438113358;O_=572947956;B_=+"2";for(var d3=1;C9["p7"](d3["toString"](),d3["toString"]()["length"],60000) !== A9;d3++){W_["spanLock"]=![];B_+=2;}if(C9["p7"](B_["toString"](),B_["toString"]()["length"],61157) !== O_){W_["spanLock"]=!!({});}}else if(t9 || L9 < 0){W_["scroll"]+=L9;this["grabStartScrollX"]+=L9;if(this["swipe"]){this["swipe"]["scroll"]+=L9;}}}if(this["transformDataSetPost"]){this["transformDataSetPost"](this,m1,U0,g5);}t3=this["maxDataSetSize"];if(t3){if(d7["length"] + m1["length"] > t3){if(m1["length"] < t3){C9["o9"](2);L8=-C9["E6"](0,"1521817598");C9["o9"](0);m2=C9["E6"]("1439655197",1);U7=2;for(var U1=1;C9["g0"](U1["toString"](),U1["toString"]()["length"],"78087" ^ 0) !== L8;U1++){d7=d7["slice"](m1["length"] + t3);U7+=2;}if(C9["g0"](U7["toString"](),U7["toString"]()["length"],42958) !== m2){d7=d7["slice"](m1["length"] - t3);}}else {d7=[];}m1=m1["slice"](-t3);}}if(!W_["scrubbed"]){W_["scrubbed"]=[];}if(d7["length"]){Z0=-603992556;C9["o9"](9);n1=C9["O2"]("626897655",0);C9["o9"](9);Y8=C9["O2"]("2",32);for(var y0=1;C9["p7"](y0["toString"](),y0["toString"]()["length"],86534) !== Z0;y0++){n7=d7[d7["length"] / +"5"]["DT"];Y8+=2;}if(C9["p7"](Y8["toString"](),Y8["toString"]()["length"],80588) !== n1){n7=d7[d7["length"] - ("1" << 32)]["DT"];}while(W_["scrubbed"]["length"] && W_["scrubbed"][W_["scrubbed"]["length"] - 1]["DT"] > n7){W_["scrubbed"]["pop"]();}}else {l2=-1090130999;C9["l7"](1);Z7=-C9["E6"]("333345961",8);T1=2;for(var T0=1;C9["p7"](T0["toString"](),T0["toString"]()["length"],59150) !== l2;T0++){W_["scrubbed"]=[];T1+=2;}if(C9["g0"](T1["toString"](),T1["toString"]()["length"],37349) !== Z7){W_["scrubbed"]=[];}}if(!W_["state"]["studies"]){W_["state"]["studies"]={};}W_["state"]["studies"]["startFrom"]=W_["scrubbed"]["length"];l5=[];for(k4=0;k4 < m1["length"];k4++){u8=m1[k4];if(u8["Close"] || u8["Close"] === 0){l5["push"](u8);}else if(u8["DT"] > Date["now"]()){l5["push"](u8);};}W_["scrubbed"]=W_["scrubbed"]["concat"](l5);if(!A1 || !W_["state"]["calculations"]){W_["state"]["calculations"]={};}this["calculateATR"](W_,20,l5);this["calculateMedianPrice"](W_,l5);this["calculateTypicalPrice"](W_,l5);this["calculateWeightedClose"](W_,l5);this["calculateOHLC4"](W_,l5);for(t_ in this["plugins"]){w2=this["plugins"][t_];if(w2["createDataSet"]){w2["createDataSet"](this,W_,m1,d7["length"]);}}W_["dataSet"]=d7["concat"](m1);for(t_=0;t_ < W_["dataSet"]["length"];t_++){W_["dataSet"][t_]["cache"]={};W_["dataSet"][t_]["tick"]=t_;}W_["whiteSpaceFutureTicks"]=0;n3=this["layout"]["studies"];e4=W_["scrubbed"]["length"];if(n3 && Object["keys"](n3)["length"]){o0=-805361612;W6=-1087882742;L_=2;for(var T7=1;C9["p7"](T7["toString"](),T7["toString"]()["length"],85360) !== o0;T7++){g4=W_["state"]["studies"]["sorted"] && b["Studies"]["sortForProcessing"](this);y6=this;W_["state"]["studies"]["sorted"]=g4;L_+=2;}if(C9["p7"](L_["toString"](),L_["toString"]()["length"],85200) !== W6){g4=W_["state"]["studies"]["sorted"] || b["Studies"]["sortForProcessing"](this);y6=this;W_["state"]["studies"]["sorted"]=g4;}g4["forEach"](function(o5){var k0=-824556555,p1=850801877,Q1=1537094964,z8=-590405085,L3=-405076524,b2=2029678205,f8=-1145634490,X4=-1932753661,k_=-2015224250,X9=1789748964;if(C9.J5(0,false,123509) === k0 || C9.j_(0,false,731516) === p1 || C9.J5(9,true,137481) === Q1 || C9.j_(9,true,759491) === z8 || C9.J5(8,true,448180) === L3 || C9.j_(10,true,222526) === b2 || C9.J5(9,true,903776) === f8 || C9.J5(9,true,117826) === X4 || C9.j_(10,true,558573) === k_ || C9.j_(8,true,440846) === X9){o5["startFrom"]=W_["state"]["studies"]["startFrom"];o5["error"]=null;if(o5["study"] && o5["study"]["calculateFN"]){o5["study"]["calculateFN"](y6,o5);}}});}for(t_=e4;t_ < W_["scrubbed"]["length"];t_++){k7=W_["scrubbed"][t_];k7["cache"]={};k7["tick"]=W_["dataSet"]["length"];W_["dataSet"]["push"](k7);}if(this["drawingObjects"]["length"]){this["adjustDrawings"]();}if(this["establishMarkerTicks"]){this["establishMarkerTicks"]();}this["runAppend"]("createDataSet",c8);}};function z(x1,M8){var h2,J2,G_,z9,I1,B8,Y9,d0,l$,p8,K_,h1,N$;if(x1.hasOwnProperty(r)){return;}h2=new Image();J2=10;C9.w7();G_=3.375;C9.o9(10);z9=C9.E6(4,5);C9.l7(10);I1=C9.E6(5,4);B8=5;C9.o9(11);var v_=C9.O2(120,32,4);C9.l7(12);var N6=C9.O2(2,2,14,12);Y9=Math.pow(z9,v_) / N6;d0=1079749825;l$=1760526295;p8=2;for(var F9=1;C9.g0(F9.toString(),F9.toString().length,67990) !== d0;F9++){C9.o9(10);K_=C9.E6(1,4);p8+=2;}if(C9.g0(p8.toString(),p8.toString().length,3696) !== l$){C9.l7(0);K_=C9.O2(7,0);}h1=K_;N$=Object.create(null,{sizeRatio:{configurable:![],enumerable:!"1",get:function(){return h1;},set:function(A4){var R3,s_,A6;C9.w7();if(A4 < Y9){h1=Y9;}else if(A4 > K_){h1=K_;}else {C9.l7(1);R3=-C9.E6("174425377",33);s_=-1893602018;A6=2;for(var V6=1;C9.p7(V6.toString(),V6.toString().length,72886) !== R3;V6++){h1=A4 && K_;C9.o9(13);A6+=C9.O2(0,"2");}if(C9.g0(A6.toString(),A6.toString().length,22169) !== s_){h1=A4 || K_;}}}},draw:{configurable:![],enumerable:![],value:function(d$){var J_,O9,Z8,g8,U$,X5,a0,q0,s0,s$,q9;if(this.image){J_=document.querySelector("cq-attrib-container")?document.querySelector("cq-attrib-container").offsetHeight:0;O9=d$.yAxis.bottom - J_ - J2;var {width:u5, height:g6}=this.image;if(isNaN(u5) || isNaN(g6)){return;}Z8=u5 * this.sizeRatio;g8=g6 * this.sizeRatio;U$=d$.left + J2;C9.l7(3);X5=C9.O2(O9,g8);a0=d$.context;q0=!({});do {if((U$ + Z8 * G_ > d$.right || g8 * B8 > O9) && this.sizeRatio > Y9){this.sizeRatio*=z9;Z8=u5 * this.sizeRatio;g8=g6 * this.sizeRatio;C9.l7(3);X5=C9.O2(O9,g8);q0=!"";}else if(U$ + u5 * (this.sizeRatio * I1) * G_ < d$.right && g6 * (this.sizeRatio * I1) * B8 < O9 && this.sizeRatio < K_){this.sizeRatio*=I1;Z8=u5 * this.sizeRatio;g8=g6 * this.sizeRatio;C9.o9(3);X5=C9.E6(O9,g8);q0=!"";}else {q0=![];}}while(q0);a0.save();var [,,S6]=b.hsl(x1.containerColor);a0.globalAlpha=S6 > 0.35?0.15:0.2;this.image.src=S6 > "0.35" - 0?this.image.darksrc:this.image.lightsrc;a0.drawImage(this.image,0,0,u5,g6,U$,X5,Z8,g8);a0.restore();this.first=!({});}else if(this.first !== !!0){s0=1027800914;s$=1159608339;q9=+"2";for(var r$="1" >> 64;C9.p7(r$.toString(),r$.toString().length,99196) !== s0;r$++){this.first=d$;C9.l7(13);q9+=C9.E6(0,"2");}if(C9.p7(q9.toString(),q9.toString().length,23096) !== s$){this.first=d$;}this.first=d$;}},writable:!"1"}});h2.onload=function(){var V2;V2="i";V2+="m";V2+="ag";V2+="e";Object.defineProperty(N$,V2,{configurable:!({}),enumerable:![],value:h2,writable:![]});if(!h2.darksrc){h2.lightsrc=h2.src;C9.o9(5);var d_=C9.O2(20,17,1066);C9.l7(14);var k9=C9.E6(10,13,647,4520,7);C9.o9(15);var G0=C9.E6(14508,15,12893,12);C9.l7(16);var c6=C9.O2(5,7410,1482,7390);C9.o9(17);var A$=C9.O2(5,5865,822,6);C9.o9(4);var H6=C9.O2(9729,11);C9.l7(18);var z5=C9.O2(3,2,3990,11,4795);h2.darksrc=M8.slice(0,d_) + (153.01 <= +"8620"?(k9,G0) >= +"1310"?"i":c6 < (A$,H6)?z5:!({}):("L",108.02)) + M8.slice(+"1064");h2.src=h2.darksrc;}else {if(N$.first){N$.first.container.stx.draw();}}};h2.src=M8;Object.defineProperty(x1,r,{configurable:![],enumerable:!1,value:N$,writable:!({})});}r=Symbol.for("CIQ.watermark");};/* eslint-enable */ /* jshint ignore:end */ /* ignore jslint end */
-
-
- let _exports = {};
- __js_core__init_(_exports);
- __js_core__polyfills_(_exports);
- __js_core_browserDetect_(_exports);
- __js_core_canvasutil_(_exports);
- __js_core_color_(_exports);
- __js_core_date_(_exports);
- __js_core_dom_(_exports);
- __js_core_engineInit_(_exports);
- __js_core_formatData_(_exports);
- __js_core_math_(_exports);
- __js_core_object_(_exports);
- __js_core_plotter_(_exports);
- __js_core_renderer_(_exports);
- __js_core_string_(_exports);
- __js_core_typedefs_(_exports);
- __js_core_xhr_(_exports);
- __js_core_engine_accessory_(_exports);
- __js_core_engine_baselines_(_exports);
- __js_core_engine_chart_(_exports);
- __js_core_engine_convert_(_exports);
- __js_core_engine_crosshair_(_exports);
- __js_core_engine_data_(_exports);
- __js_core_engine_event_(_exports);
- __js_core_engine_injection_(_exports);
- __js_core_engine_misc_(_exports);
- __js_core_engine_panel_(_exports);
- __js_core_engine_periodicity_(_exports);
- __js_core_engine_record_(_exports);
- __js_core_engine_render_(_exports);
- __js_core_engine_styles_(_exports);
- __js_core_engine_xaxis_(_exports);
- __js_core_engine_yaxis_(_exports);
- __js_core_engine_obfuscate_data_(_exports);
- __js_core_engine_obfuscate_render_(_exports);
- __js_core_engine_obfuscate_scroll_(_exports);
- __js_core_engine_obfuscate_xaxis_(_exports);
- __js_core_engine_obfuscate_yaxis_(_exports);
-
- let {CIQ, SplinePlotter, timezoneJS, $$, $$$} = _exports;
- export {CIQ, SplinePlotter, timezoneJS, $$, $$$};
\ No newline at end of file
diff --git a/chartiq/development/js/deprecated.js b/chartiq/development/js/deprecated.js
deleted file mode 100644
index c15ec4ecae..0000000000
--- a/chartiq/development/js/deprecated.js
+++ /dev/null
@@ -1,2864 +0,0 @@
-/**!
- * 8.2.0
- * Generation date: 2023-03-23T15:05:01.971Z
- * Client name: deriv limited
- * Package Type: Technical Analysis
- * License type: annual
- * Expiration date: "2024/04/01"
- * Domain lock: ["127.0.0.1","localhost","deriv.com","deriv.app","deriv.me","binary.com","binary.sx","binary.me","binary.bot","deriv.be"]
- * iFrame lock: true
- */
-
-/***********************************************************!
- * Copyright by ChartIQ, Inc.
- * Licensed under the ChartIQ, Inc. Developer License Agreement https://www.chartiq.com/developer-license-agreement
-*************************************************************/
-/*************************************! DO NOT MAKE CHANGES TO THIS LIBRARY FILE!! !*************************************
-* If you wish to overwrite default functionality, create a separate file with a copy of the methods you are overwriting *
-* and load that file right after the library has been loaded, but before the chart engine is instantiated. *
-* Directly modifying library files will prevent upgrades and the ability for ChartIQ to support your solution. *
-*************************************************************************************************************************/
-/* eslint-disable no-extra-parens */
-
-
-/***********************************************************!
- * Copyright by ChartIQ, Inc.
- * Licensed under the ChartIQ, Inc. Developer License Agreement https://www.chartiq.com/developer-license-agreement
-*************************************************************/
-/*************************************! DO NOT MAKE CHANGES TO THIS LIBRARY FILE!! !*************************************
-* If you wish to overwrite default functionality, create a separate file with a copy of the methods you are overwriting *
-* and load that file right after the library has been loaded, but before the chart engine is instantiated. *
-* Directly modifying library files will prevent upgrades and the ability for ChartIQ to support your solution. *
-*************************************************************************************************************************/
-import {CIQ} from "../js/chartiq.js";
-
-
-
-/*
- Deprecated functions - lite
-*/
-
-var WARN_INTERVAL = 10000;
-/**
- * Warn developer of use of deprecated function.
- *
- * All deprecated functions should be calling this log whenever it is used.
- * Warning will be output to the console up to 10 seconds after it is logged, in order to help suppress duplicates.
- *
- * @param {string} message Message to output
- * @private
- */
-var warnings = null;
-CIQ.deprecationWarning = function (message) {
- if (!warnings) {
- warnings = {};
- setInterval(function () {
- for (var m in warnings) {
- console.warn(m + " (" + warnings[m] + " occurrences)");
- delete warnings[m];
- }
- }, WARN_INTERVAL);
- }
- if (!warnings[message]) warnings[message] = 1;
- else warnings[message]++;
-};
-
-var log = CIQ.deprecationWarning;
-
-/* Function.ciqInheritsFrom, Function.stxInheritsFrom */
-if (!Function.prototype.ciqInheritsFrom) {
- /**
- * The built-in Function object.
- * @external Function
- * @see [Function]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function} on the Mozilla Developer Network.
- */
-
- /**
- * **Deprecated since 7.4.0.** Use {@link CIQ.inheritsFrom} instead.
- *
- * Template for JavaScript inheritance.
- *
- * By default the constructor (ctor) is called with no arguments.
- *
- * @param {object} ctor The parent class or object.
- * @param {boolean} [autosuper=true] Set to false to prevent the base class constructor from being called automatically.
- * @memberof external:Function
- * @alias external:Function#ciqInheritsFrom
- * @deprecated As of 7.4.0. Use {@link CIQ.inheritsFrom} instead.
- */
- Function.prototype.ciqInheritsFrom = function (ctor, autosuper) {
- log(
- "Function.prototype.ciqInheritsFrom() has been deprecated. Use CIQ.inheritsFrom(me, ctor, autosuper) instead."
- );
- CIQ.inheritsFrom(this, ctor, autosuper);
- };
- Function.prototype.stxInheritsFrom = Function.prototype.ciqInheritsFrom; // backward compatibility
-}
-
-/**
- * The built-in String object.
- * @external String
- * @see [String]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String} on the Mozilla Developer Network.
- */
-
-/**
- * **Deprecated since 7.4.0.** Use {@link CIQ.capitalize} instead.
- *
- * Capitalizes the first letter of a string.
- *
- * @return {string} Capitalized version of the string.
- * @memberof external:String
- * @alias external:String#capitalize
- * @deprecated As of 7.4.0. Use {@link CIQ.capitalize} instead.
- */
-String.prototype.capitalize = function () {
- log(
- "String.prototype.capitalize() has been deprecated. Use CIQ.capitalize(string) instead."
- );
- return CIQ.capitalize(this);
-};
-
-/**
- * **Deprecated since 7.4.0.** Use native CanvasRenderingContext2D methods such as moveTo(), lineTo() and setLineDash() instead.
- *
- * Dashed line polyfill for the canvas. Note that dashed lines are expensive operations when not supported natively.
- * See {@link external:CanvasRenderingContext2D#stxLine}.
- *
- * @param {number} fromX Starting point of the X-axis.
- * @param {number} fromY Starting point of the Y-axis.
- * @param {number} toX Destination on the X-axis.
- * @param {number} toY Destination on the Y-axis.
- * @param {string[]} pattern Array of stroke patterns.
- * @memberof external:CanvasRenderingContext2D
- * @alias external:CanvasRenderingContext2D#dashedLineTo
- * @deprecated As of 7.4.0. Use native CanvasRenderingContext2D methods such as moveTo(), lineTo() and setLineDash() instead.
- *
- * @example
- *
Use {@link CIQ.ChartEngine#pixelFromTransformedValue} to get the pixel location based on the transformed value (percentage comparison change, for example).
- *
- * @param {number} price The price or value
- * @param {CIQ.ChartEngine.Panel} panel A panel object (see {@link CIQ.ChartEngine#pixelFromPrice})
- * @param {CIQ.ChartEngine.YAxis} [yAxis] The yaxis to use
- * @return {number} The y axis pixel location
- * @memberof CIQ.ChartEngine
- * @deprecated Use {@link CIQ.ChartEngine#pixelFromPrice} instead
- * @since 4.0.0 Now behaves like pixelFromPriceTransform. That is, on a comparison chart, pixelFromPrice accepts an actual stock price, not a percentage value.
- */
-CIQ.ChartEngine.prototype.pixelFromPriceTransform = function (
- price,
- panel,
- yAxis
-) {
- log(
- "CIQ.ChartEngine.prototype.pixelFromPriceTransform has been deprecated. Use CIQ.ChartEngine.prototype.pixelFromPrice instead."
- );
- return this.pixelFromPrice(price, panel, yAxis);
-};
-
-//deprecated, use static version
-CIQ.ChartEngine.prototype.isDailyInterval = function (interval) {
- log(
- "CIQ.ChartEngine.prototype.isDailyInterval has been deprecated. Use CIQ.ChartEngine.isDailyInterval instead."
- );
- return CIQ.ChartEngine.isDailyInterval(interval);
-};
-
-/**
- * Renders a chart for a particular instrument from the data passed in or fetches new data from the attached {@link quotefeed}.
- *
- * This method has been deprecated, use {@link CIQ.ChartEngine#loadChart}.
- *
- * @param {string|object} symbol The symbol or equation for the new chart - a symbol string, equation or an object representing the symbol can be used.
- *
After the new chart is initialized, it will contain both a symbol string (stxx.chart.symbol) and a symbol object (stxx.chart.symbolObject).
- *
You can send anything you want in the symbol object, but you must always include at least a 'symbol' element. Both these variables will be available for use wherever the {@link CIQ.ChartEngine.Chart} object is present. For example, if using a {@link quotefeed} for gathering data, params.stx.chart.symbolObject will contain your symbol object.
- *
To allow equations to be used on a chart, the {@link CIQ.ChartEngine#allowEquations} parameter must be set to `true` and the equation needs to be preceded by an equals sign (=) in order for it to be parsed as an equation.
- *
See {@link CIQ.formatEquation} and {@link CIQ.computeEquationChart} for more details on allowed equations syntax.
- * @param {array} [masterData] An array of [properly formatted OHLC objects]{@tutorial InputDataFormat} to create a chart. Each element should at a minimum contain a "Close" field (capitalized).
- * If the charting engine has been configured to use a [QuoteFeed]{@link CIQ.ChartEngine#attachQuoteFeed}
- * then masterData does not need to be passed in. The quote feed will be queried instead.
- * @param {CIQ.ChartEngine.Chart} chart] Which chart to create. Defaults to the default chart.
- * @param {function} [cb] Callback when newChart is loaded. See {@tutorial Adding additional content on chart} for a tutorial on how to use this callback function.
- * @param {object} [params] Parameters to dictate initial rendering behavior
- * @param {Object} [params.range] Default range to be used upon initial rendering. If both `range` and `span` parameters are passed in, range takes precedence. If periodicity is not set, the range will be displayed at the most optimal periodicity. See {@link CIQ.ChartEngine#setRange} for complete list of parameters this object will accept.
- * @param {object} [params.span] Default span to display upon initial rendering. If both `range` and `span` parameters are passed in, range takes precedence. If periodicity is not set, the span will be displayed at the most optimal periodicity. See {@link CIQ.ChartEngine#setSpan} for complete list of parameters this object will accept.
- * @param {object} [params.periodicity] Periodicity to be used upon initial rendering. See {@link CIQ.ChartEngine#setPeriodicity} for complete list of parameters this object will accept. If no periodicity has been set it will default to `1 day`.
- * @param {boolean} [params.stretchToFillScreen] Increase the candleWidth to fill the left-side gap created by a small dataSet. Respects CIQ.ChartEngine.preferences.whitespace. Ignored when params `span` or `range` are used. See {@link CIQ.ChartEngine#fillScreen}
- * @memberof CIQ.ChartEngine
- * @example
- * // using a symbol object and embedded span and periodicity requirements
- * stxx.newChart(
- * {symbol:newSymbol,other:'stuff'},
- * null,
- * null,
- * callbackFunction,
- * {
- * span:{base:'day',multiplier:2},
- * periodicity:{period:1,interval:5,timeUnit:'minute'},
- * stretchToFillScreen:true
- * }
- * );
- *
- * @example
- * // using a symbol string
- * stxx.newChart(
- * "IBM",
- * null,
- * null,
- * callbackFunction
- * );
- *
- * @example
- * // using an equation string
- * stxx.newChart(
- * "=2*IBM-GM",
- * null,
- * null,
- * callbackFunction
- * );
- *
- * @deprecated use {@link CIQ.ChartEngine#loadChart}
- * @since
- * - 2015-11-1 newChart is capable of setting periodicity and span via `params` settings.
- * - 04-2016-08 Added `params.stretchToFillScreen`.
- * - 5.1.0 newChart is capable of setting range via `params` settings.
- * - 6.0.0 Statically provided data will be gap-filled if that functionality is enabled.
- * - 7.0.0 Deprecated, replaced by {@link CIQ.ChartEngine#loadChart}.
- */
-CIQ.ChartEngine.prototype.newChart = function (
- symbol,
- masterData,
- chart,
- cb,
- params
-) {
- log(
- "CIQ.ChartEngine.prototype.newChart has been deprecated. Use CIQ.ChartEngine.prototype.loadChart instead."
- );
- var parameters = {
- masterData: masterData,
- chart: chart
- };
- CIQ.extend(parameters, params, true);
- // console.log('WARNING: newChart is deprecated for removal. Please use loadChart.');
- return this.loadChart(symbol, parameters, cb);
-};
-
-/**
- * Streams "last sale" prices into the chart.
- *
- *
- * >**This function has been deprecated in favor of {@link CIQ.ChartEngine#updateChartData}.
- * This also means that {@link CIQ.ChartEngine#streamParameters.fillGaps} is deprecated.
- * Developers should instead call {@link CIQ.ChartEngine#updateChartData} with `params.fillGaps=true` or rely on cleanupGaps as default behavior.**
- *
- * >`streamTrade` to `updateChartData` migration examples:
- *
- * >Note that updateChartData follows the 'OHLC' format.
- * So `V`olume (upper case) is used rather than `v`olume (lower case).
- * Similarly `L`ast (upper case) is used rather than `l`ast (lower case).
- *
- * >Example 1: streaming a secondary series:
- *
- * >`streamTrade({"last":102.05}, null, "IBM");`
- *
Translates to :
- * `updateChartData({"Last":102.05}, null, {fillGaps: true, secondarySeries: "IBM"});`
- *
- * >Example 2: streaming a primary series:
- *
- * >`streamTrade({"last":102.05, "volume":100});`
- *
Translates to :
- * `updateChartData({"Last": 102.05,"Volume":100}, null, {fillGaps: true});`
- *
- * This method is designed to append ticks to the master data while maintaining the existing periodicity, appending to the last tick or creating new ticks as needed.
- * It will also fill in gaps if there are missing bars in a particular interval.
- * If a trade has a date older than the beginning of the next bar, the last bar will be updated even if the trade belongs to a prior bar; this could happen if a trade is sent in after hours at a time when the market is closed, or if it is received out of order.
- * When in 'tick' interval, each trade will be added to a new bar and no aggregation to previous bars will be done.
- * If the optional timestamp [now] is sent in, and it is older than the next period to be rendered, the last tick on the dataset will be updated instead of creating a new tick.
- *
- * **It is crucial that you ensure the date/time of the trade is in line with your `masterData` and `dataZone`** See `now` parameter for more details.
- *
- * This method leverages {@link CIQ.ChartEngine#updateChartData} for the actual data insertion into masterData. Please see {@link CIQ.ChartEngine#updateChartData} for additional details and performance throttle settings.
- *
- * See the [Streaming]{@tutorial DataIntegrationStreaming} tutorial for more the details.
- *
- * **Note:** versions prior to 15-07-01 must use the legacy arguments : streamTrade(price, volume, now, symbol)
- *
- * @param {object} data Price & Volume Data, may include any or all of the following:
- * @param {number} data.last Last sale price
- * @param {number} [data.volume] Trade volume
- * @param {number} [data.bid] Bid price
- * @param {number} [data.ask] Offer/Ask price
- * @param {date} [now] Date of trade. It must be a java script date [new Date().getTime()]. **If omitted, defaults to "right now" in the set `dataZone`** (see {@link CIQ.ChartEngine#setTimeZone}); or if no `dataZone` is set, it will default to the browser's timezone (not recommended for international client-base since different users will see different times). It is important to note that this value must be in the same timezone as the rest of the masterData already sent into the charting engine to prevent tick gaps or overlaps.
- * @param {string} [symbol] trade symbol for series streaming ONLY. Leave out or set to `null` when streaming the primary chart symbol.
- * @param {object} [params] Params to be passed to {@link CIQ.ChartEngine#updateChartData}
- * @memberof CIQ.ChartEngine
- * @example
- * // streaming last sale for the primary chart symbol
- * stxx.streamTrade({"last":102.05, "volume":100});
- * @example
- * // streaming last sale for an additional series on the chart
- * stxx.streamTrade({"last":102.05, "volume":100}, null, "IBM");
- * @deprecated Please use {@link CIQ.ChartEngine#updateChartData} for streaming last ticket.
- * @since 4.0.0 Deprecated this function. This also means that streamParameters.fillGaps is deprecated. Developers should
- * call {@link CIQ.ChartEngine#updateChartData} with `params.fillGaps=true` or rely on cleanupGaps as default behavior.
- */
-CIQ.ChartEngine.prototype.streamTrade = function (
- priceData,
- now,
- symbol,
- params
-) {
- log(
- "CIQ.ChartEngine.prototype.streamTrade has been deprecated. Use CIQ.ChartEngine.prototype.updateChartData instead."
- );
- var chart = this.chart;
- if (!params) params = {};
- if (params.chart) chart = params.chart;
- params.fillGaps = this.streamParameters.fillGaps;
- var newArgs = typeof priceData == "object";
-
- var price = newArgs ? priceData.last : arguments[0],
- volume = newArgs ? priceData.volume : arguments[1],
- bid = newArgs ? priceData.bid : null,
- ask = newArgs ? priceData.ask : null;
-
- if (!newArgs) {
- now = arguments[2];
- symbol = arguments[3];
- }
-
- if (symbol) {
- //series element
- params.secondarySeries = symbol;
- }
-
- var data = {
- DT: now,
- Last: price,
- Volume: volume,
- Bid: bid,
- Ask: ask
- };
-
- this.updateChartData(data, chart, params);
-};
-
-/**
- * As of version 5.1, his method has been **deprecated** in favor of {@link CIQ.ChartEngine#updateChartData} which provides improved functionality.
- *
- * The following parameters are only applicable for legacy versions (pre 5.1):
- * @deprecated Please use {@link CIQ.ChartEngine#updateChartData}
- * @param {array/object} appendQuotes An array of properly formatted OHLC quote object(s). [See Data Format]{@tutorial InputDataFormat}.
- * Or a last sale object with the following elements:
- * @param {number} appendQuotes.Last Last sale price
- * @param {number} [appendQuotes.Volume] Trade volume
- * @param {number} [appendQuotes.Bid] Bid price
- * @param {number} [appendQuotes.Ask] Offer/Ask price
- * @param {number} [appendQuotes.DT] Date of trade.
- * It must be a java script date [new Date().getTime()].
- * **If omitted, defaults to "right now" in the set `dataZone`** (see {@link CIQ.ChartEngine#setTimeZone});
- * or if no `dataZone` is set, it will default to the browser's timezone (not recommended for international client-base since different users
- * will see different times). It is important to note that this value must be in the same timezone as the rest of the masterData already
- * sent into the charting engine to prevent tick gaps or overlaps.
- * @param {CIQ.ChartEngine.Chart} [chart] The chart to append the quotes. Defaults to the default chart.
- * @param {object} [params] Parameters to dictate behavior
- * @param {boolean} [params.noCreateDataSet] If true then do not create the data set automatically, just add the data to the masterData
- * @param {boolean} [params.allowReplaceOHL] Set to true to bypass internal logic that maintains OHL
- * @param {boolean} [params.bypassGovernor] If true then masterdata will be immediately updated regardless of {@link CIQ.ChartEngine#streamParameters}
- * @param {boolean} [params.fillGaps] If true then {@link CIQ.ChartEngine#doCleanupGaps} is called using the {@link CIQ.ChartEngine#cleanupGaps} setting. This will ensure gaps will be filled in the master data from the last tick in the chart to the date of the trade.
Reminder: `tick` does not fill any gaps as it is not a predictable interval.
- * @param {boolean} [params.secondarySeries] Set to the name of the element ( valid comparison symbol, for example) to load data as a secondary series.
- * @param {boolean} [params.useAsLastSale] If not using a 'last sale' formatted object in `appendQuotes`,
- * you can simply set this parameter to `true` to force the data as a last sale price; or further define it by creating an object including other settings as needed.
- * This option is available in cases when a feed may always return OHLC formatted objects or a 'Close' field instead of a 'Last' field,
- * even for last sale streaming updates.
- * By definition a 'last' sale can only be a single record indicating the very 'last' sale price. As such, even if multiple records are sent in the `appendQuotes` array when this flag is enabled,
- * only the last record's data will be used. Specifically the 'Close' and 'Volume' fields will be streamed.
- * @param {boolean} [params.useAsLastSale.aggregatedVolume] If your last sale updates send current volume for the bar instead of just the trade volume, set this parameter to 'true' in the `params.useAsLastSale` object. The sent in volume will be used as is instead of being added to the existing bar's volume.
- *
- * @memberof CIQ.ChartEngine
- * @since
- * - 2015-11-1 Added `params.bypassGovernor` and `params.allowReplaceOHL`.
- * - 2015-11-1 Deprecated `params.force`. Every call will update the tick to maintain the proper volume, and `createDataSet` is now controlled by `sp.maxTicks`, `sp.timeout`, or `params.bypassGovernor`.
- * - 3.0.0 Now `appendQuotes` also takes last sale data to allow streaming capabilities. This can now be used instead of streamTrade.
- * - 3.0.0 New `params.fillGaps`, `params.secondarySeries`, and `params.useAsLastSale`.
- * - 4.0.0 Last sale streaming will now update a bar in the past to comply with the date sent in instead of just updating the current tick.
- * - 4.0.3 Added `params.useAsLastSale.aggregatedVolume`.
- * - 5.0.1 Now calls doCleanupDates in case is is being called directly when not using a quoteFeed, to update an entire candle.
- */
-CIQ.ChartEngine.prototype.appendMasterData = function (
- appendQuotes,
- chart,
- params
-) {
- log(
- "CIQ.ChartEngine.prototype.appendMasterData has been deprecated. Use CIQ.ChartEngine.prototype.updateChartData instead."
- );
- this.updateChartData(appendQuotes, chart, params);
-};
-
-/**
- * INJECTABLE
- *
- * **Legacy** function to set the periodicity and interval for the chart.
- *
- * **Replaced by {@link CIQ.ChartEngine#setPeriodicity}, but maintained for backwards comparibility. Uses same function signature.**
- *
- * @param {number} period The number of elements from masterData to roll-up together into one data point on the chart (one candle, for example). If set to 30 in a candle chart, for example, each candle will represent 30 raw elements of `interval` type.
- * @param {string} interval The type of data to base the `period` on. This can be a numeric value representing minutes, seconds or millisecond as inicated by `timeUnit`, "day","week", "month" or 'tick' for variable time x-axis. **"hour" is NOT a valid interval.** (This is not how much data you want the chart to show on the screen; for that you can use {@link CIQ.ChartEngine#setRange} or {@link CIQ.ChartEngine#setSpan})
- * @param {string} [timeUnit] Time unit to further qualify the specified numeric interval. Valid values are "millisecond","second","minute",null. If not set, will default to "minute". **only applicable and used on numeric intervals**
- * @param {function} [cb] Callback after periodicity is changed. First parameter of callback will be null unless there was an error.
- *
- * @memberof CIQ.ChartEngine
- * @since
- * - 2015-11-1 Second and millisecond periodicities are now supported by setting the `timeUnit` parameter.
- * - 3.0.0 Replaced by {@link CIQ.ChartEngine#setPeriodicity}, but maintained for backwards comparibility.
- * - 8.0.0 Deprecated
- * @deprecated Use {@link CIQ.ChartEngine#setPeriodicity}.
- */
-CIQ.ChartEngine.prototype.setPeriodicityV2 = function (
- period,
- interval,
- timeUnit,
- cb
-) {
- log(
- "CIQ.ChartEngine.prototype.setPeriodicityV2 has been deprecated. Use CIQ.ChartEngine.prototype.setPeriodicity instead."
- );
- if (typeof timeUnit === "function") {
- cb = timeUnit; // backward compatibility
- timeUnit = null;
- }
- if (this.runPrepend("setPeriodicityV2", arguments)) return;
- this.setPeriodicity(period, interval, timeUnit, cb);
- this.runAppend("setPeriodicityV2", arguments);
-};
-
-//Unused
-CIQ.ChartEngine.prototype.addChart = function (name, chart) {
- log(
- "CIQ.ChartEngine.prototype.addChart has been deprecated. Add manually to stxx.charts object instead."
- );
- chart.name = name;
- this.charts[name] = chart;
-};
-
-var changeOccurred = CIQ.ChartEngine.prototype.changeOccurred;
-CIQ.ChartEngine.prototype.changeOccurred = function (change) {
- if (this.currentlyImporting) return;
- if (this.changeCallback) this.changeCallback(this, change);
- changeOccurred.call(this, change);
-};
-
-var engineConstruct = CIQ.ChartEngine.prototype.construct;
-CIQ.ChartEngine.prototype.construct = function () {
- function getValue(obj, name, def) {
- if (!obj._deprecatedPropertyValues) obj._deprecatedPropertyValues = {};
- if (!(name in obj._deprecatedPropertyValues))
- obj._deprecatedPropertyValues[name] = def;
- return obj._deprecatedPropertyValues[name];
- }
- function setValue(obj, name, val) {
- if (!obj._deprecatedPropertyValues) obj._deprecatedPropertyValues = {};
- obj._deprecatedPropertyValues[name] = val;
- }
-
- engineConstruct.call(this);
-
- Object.defineProperties(this, {
- /**
- * **This function has been deprecated. Please use {@link CIQ.ChartEngine#addEventListener} instead.**
- *
- * This is the callback function used to react to {@link CIQ.ChartEngine#changeOccurred}.
- *
- * Use this for storing chart configurations or drawings real time as users make changes.
- *
- * Expected format :
- *
- * fc(stxChart, eventType);
- *
- * Currently implemented values for "eventType" are "layout" and "vector".
- *
- * You can create any additional event types and trigger them by calling 'CIQ.ChartEngine.changeOccurred(eventType)'
- *
- * **Note** only one changeCallback function can be registered per chart object. As such, you must program it to handle any and all possible events triggered by {@link CIQ.ChartEngine#changeOccurred}.
- * @type {function}
- * @alias changeCallback
- * @memberof CIQ.ChartEngine.prototype
- * @deprecated
- * @since 4.0.0 Deprecated
- * @example
- * stxx.changeCallback=function(stxx, eventType){
- * if(eventType=="layout") saveLayout();
- * if(eventType=="vector") saveDrawing();
- * }
- */
- changeCallback: {
- enumerable: true,
- get: (function (stx) {
- return function () {
- //log("CIQ.ChartEngine.prototype.changeCallback has been deprecated. Use CIQ.ChartEngine.prototype.addEventListener instead.");
- return getValue(this, "changeCallback", null);
- };
- })(this),
- set: (function (stx) {
- return function (func) {
- log(
- "CIQ.ChartEngine.prototype.changeCallback has been deprecated. Use CIQ.ChartEngine.prototype.addEventListener instead."
- );
- setValue(this, "changeCallback", func);
- };
- })(this)
- },
- /**
- * Chart types which plot more than one data field (OHLC charts).
- * Putting a chart type here will disable the use of {@link CIQ.ChartEngine.Chart#defaultPlotField}.
- * @type object
- * @default
- * @alias highLowBars
- * @deprecated, access property in chart instead (stxx.chart.highLowBars)
- * @memberof CIQ.ChartEngine.prototype
- * @since 4.0.0
- */
- highLowBars: {
- enumerable: true,
- get: (function (stx) {
- return function () {
- log(
- "CIQ.ChartEngine.prototype.highLowBars is no longer supported. Use CIQ.ChartEngine.Chart.prototype.highLowBars instead."
- );
- return getValue(this, "highLowBars", {
- bar: true,
- colored_bar: true,
- candle: true,
- hollow_candle: true,
- volume_candle: true,
- hlc: true,
- colored_hlc: true,
- hlc_box: true,
- hlc_shaded_box: true,
- wave: true,
- rangechannel: true,
- none: true
- });
- };
- })(this),
- set: (function (stx) {
- return function (val) {
- log(
- "CIQ.ChartEngine.prototype.highLowBars is no longer supported. Use CIQ.ChartEngine.Chart.prototype.highLowBars instead."
- );
- setValue(this, "highLowBars", val);
- };
- })(this)
- },
- /**
- * Chart types whose bars represent a stand-alone entity as opposed to a vertex in a line-type chart.
- * This is important when the engine tries to render the data points right off the chart; in a stand-alone bar,
- * the points right off the chart need not be considered.
- * @type object
- * @default
- * @alias standaloneBars
- * @deprecated, access property in chart instead (stxx.chart.standaloneBars)
- * @memberof CIQ.ChartEngine.prototype
- * @since 4.0.0
- */
- standaloneBars: {
- enumerable: true,
- get: (function (stx) {
- return function () {
- log(
- "CIQ.ChartEngine.prototype.standaloneBars is no longer supported. Use CIQ.ChartEngine.Chart.prototype.standaloneBars instead."
- );
- return getValue(this, "standaloneBars", {
- bar: true,
- colored_bar: true,
- candle: true,
- hollow_candle: true,
- volume_candle: true,
- hlc: true,
- colored_hlc: true,
- hlc_box: true,
- hlc_shaded_box: true,
- histogram: true,
- scatterplot: true
- });
- };
- })(this),
- set: (function (stx) {
- return function (val) {
- log(
- "CIQ.ChartEngine.prototype.standaloneBars is no longer supported. Use CIQ.ChartEngine.Chart.prototype.standaloneBars instead."
- );
- setValue(this, "standaloneBars", val);
- };
- })(this)
- },
- /**
- * Chart types whose bars have width, as opposed to a line-type chart whose "bars" are just a point on the chart.
- * This is useful when the engine adjusts the chart for smooth scrolling and homing.
- * @type object
- * @default
- * @alias barsHaveWidth
- * @deprecated, access property in chart instead (stxx.chart.barsHaveWidth)
- * @memberof CIQ.ChartEngine.prototype
- * @since 4.0.0
- */
- barsHaveWidth: {
- enumerable: true,
- get: (function (stx) {
- return function () {
- log(
- "CIQ.ChartEngine.prototype.barsHaveWidth is no longer supported. Use CIQ.ChartEngine.Chart.prototype.barsHaveWidth instead."
- );
- return getValue(this, "barsHaveWidth", {
- bar: true,
- colored_bar: true,
- candle: true,
- hollow_candle: true,
- volume_candle: true,
- hlc: true,
- colored_hlc: true,
- hlc_box: true,
- hlc_shaded_box: true,
- histogram: true,
- scatterplot: true,
- wave: true
- });
- };
- })(this),
- set: (function (stx) {
- return function (val) {
- log(
- "CIQ.ChartEngine.prototype.barsHaveWidth is no longer supported. Use CIQ.ChartEngine.Chart.prototype.barsHaveWidth instead."
- );
- setValue(this, "barsHaveWidth", val);
- };
- })(this)
- }
- });
-
- /**
- * Specify a callback by assigning a function to the event. Once the event triggers the callback will be executed.
- *
- * **Note: All callbacks have been deprecated in favor of {@link CIQ.ChartEngine#addEventListener}**
- *
- * @type object
- * @alias callbacks
- * @memberof CIQ.ChartEngine#
- * @example
- * // using event listener
- * stxx.addEventListener("callbackNameHere", function(callBackParametersHere){
- * CIQ.alert('triggered!');
- * });
- * @example
- * // using callback function
- * stxx.callbacks.callbackNameHere=function(callBackParametersHere){
- * CIQ.alert('triggered!');
- * };
- * @deprecated 4.0.0
- */
- this.callbacks = {};
-
- function callbackGetter(name, stx, def) {
- return function () {
- log(
- "CIQ.ChartEngine.prototype.callbacks have been deprecated. Iterate through CIQ.ChartEngine.prototype.callbackListeners instead."
- );
- return getValue(this, name, def || null);
- };
- }
-
- function callbackSetter(name, stx) {
- return function (func) {
- log(
- "CIQ.ChartEngine.prototype.callbacks have been deprecated. Utilize CIQ.ChartEngine.prototype.addEventListener to add a callback, and/or CIQ.ChartEngine.prototype.removeEventListener to remove one."
- );
- if (!this._deprecatedPropertyValues) this._deprecatedPropertyValues = {};
- var origFunc = this._deprecatedPropertyValues[name];
- if (origFunc) stx.removeEventListener(name, origFunc);
- if (func) stx.addEventListener(name, func);
- this._deprecatedPropertyValues[name] = func;
- };
- }
-
- Object.defineProperties(this.callbacks, {
- /**
- * Called when a user right clicks on an overlay study. If `forceEdit==true` then a user has clicked
- * on an edit button (cog wheel) so pull up an edit dialog. Otherwise they have simply right clicked so
- * give them a context menu.
- *
- * ***Please note that this callback must be set *before* you call {@link CIQ.ChartEngine#importLayout}.
- * Otherwise your imported studies will not have an edit capability***
- *
- * Format:
- * studyOverlayEdit({stx:stx,sd:sd,inputs:inputs,outputs:outputs, parameters:parameters, forceEdit: forceEdit});
- *
- * The following CSS entry must also be present to enable the `Right click to Manage` text on the mouse-over context menu (div class="mSticky" generated by {@link CIQ.ChartEngine.htmlControls}):
- * ```
- * .rightclick_study .mouseManageText {
- * display: inline; }
- * ```
- * See {@link CIQ.Studies.addStudy} for more details.
- *
- * @type function
- * @alias CIQ.ChartEngine#callbacks[`studyOverlayEdit`]
- * @memberof CIQ.ChartEngine#callbacks
- */
- studyOverlayEdit: {
- enumerable: true,
- get: callbackGetter("studyOverlayEdit", this),
- set: callbackSetter("studyOverlayEdit", this)
- },
- /**
- * Called when a user clicks the edit button on a study panel.
- *
- * ***Please note that this callback should be set *before* you call {@link CIQ.ChartEngine#importLayout}.
- * Otherwise your imported studies will not have an edit capability***
- *
- * Format:
- * studyPanelEdit({stx:stx,sd:sd,inputs:inputs,outputs:outputs, parameters:parameters});
- *
- * See {@link CIQ.Studies.addStudy} for more details.
- *
- * @type function
- * @alias CIQ.ChartEngine#callbacks[`studyPanelEdit`]
- * @memberof CIQ.ChartEngine#callbacks
- */
- studyPanelEdit: {
- enumerable: true,
- get: callbackGetter("studyPanelEdit", this),
- set: callbackSetter("studyPanelEdit", this)
- },
- /**
- * Called when a user clicks or taps on the chart. Not called if a drawing tool is active!
- *
- * Format:
- * callback({stx:CIQ.ChartEngine, panel:CIQ.ChartEngine.Panel, x:this.cx, y:this.cy})
- * @type function
- * @alias CIQ.ChartEngine#callbacks[`tap`]
- * @memberof CIQ.ChartEngine#callbacks
- * @example
- * // using event listener
- * stxx.addEventListener("tap", function(tapObject){
- * CIQ.alert('tap event at x: ' + tapObject.x + ' y: '+ tapObject.y);
- * });
- * @example
- * // using callback
- * // this example uses barFromPixel() to get the actual bar from the pixel location
- * stxx.callbacks.tap= function(tapObject){
- * var msg= 'tap event at x: ' + tapObject.x + ' y: '+ tapObject.y;
- * var bar=this.barFromPixel(this.cx);
- * if(this.chart.dataSegment[bar]) {
- * msg+=' Date:' + this.chart.dataSegment[bar].DT;
- * msg+=' Close:' + this.chart.dataSegment[bar].Close;
- * }
- * alert (msg);
- * };
- */
- tap: {
- enumerable: true,
- get: callbackGetter("tap", this),
- set: callbackSetter("tap", this)
- },
- /**
- * Called when a user clicks or right clicks on the chart. Not called if the user right clicks on a drawing or study
- * when [stxx.bypassRightClick]{@link CIQ.ChartEngine#bypassRightClick}=true
- *
- * Format:
- * callback({stx:CIQ.ChartEngine, panel:CIQ.ChartEngine.Panel, x:this.cx, y:this.cy})
- * @type function
- * @alias CIQ.ChartEngine#callbacks[`rightClick`]
- * @memberof CIQ.ChartEngine#callbacks
- * @example
- * // using event listener
- * stxx.addEventListener("rightClick", function(rcObject){
- * alert('right click event at x: ' + rcObject.x + ' y: '+ rcObject.y);
- * });
- * @since 09-2016-19
- */
- rightClick: {
- enumerable: true,
- get: callbackGetter("rightClick", this),
- set: callbackSetter("rightClick", this)
- },
- /**
- * Called when a user "long holds" on the chart. By default this is set to 700 milliseconds.
- * Optionally change the value of stxx.longHoldTime to a different setting, or set to zero to disable.
- *
- * Format:
- * callback({stx:CIQ.ChartEngine, panel:CIQ.ChartEngine.Panel, x:this.cx, y:this.cy})
- * @type function
- * @alias CIQ.ChartEngine#callbacks[`longhold`]
- * @memberof CIQ.ChartEngine#callbacks
- * @example
- * // using event listener
- * stxx.longHoldTime=... // Optionally override default value of 700ms
- * stxx.addEventListener("longhold", function(lhObject){
- * CIQ.alert('longhold event at x: ' + lhObject.x + ' y: '+ lhObject.y);
- * });
- * @example
- * // using callback function
- * stxx.longHoldTime=... // Optionally override default value of 700ms
- * stxx.callbacks.longhold=function(lhObject){
- * CIQ.alert('longhold event at x: ' + lhObject.x + ' y: '+ lhObject.y);
- * });
- * @since 2016-06-22
- */
- longhold: {
- enumerable: true,
- get: callbackGetter("longHold", this),
- set: callbackSetter("longHold", this)
- },
- /**
- * Called when a user moves on the chart. Not called if a drawing tool is active, panel resizing, etc
- * grab is true if a mouse user has the mouse button down while moving. For touch users it is true
- * if they do not have the crosshair tool enabled.
- *
- * Format:
- * callback({stx:CIQ.ChartEngine, panel:CIQ.ChartEngine.Panel, x:this.cx, y:this.cy, grab:boolean})
- * @type function
- * @alias CIQ.ChartEngine#callbacks[`move`]
- * @memberof CIQ.ChartEngine#callbacks
- */
- move: {
- enumerable: true,
- get: callbackGetter("move", this),
- set: callbackSetter("move", this)
- },
- /**
- * Called when the layout changes
- *
- * Format:
- * callback({stx:CIQ.ChartEngine, chart:CIQ.ChartEngine.Chart, symbol: String, symbolObject:Object, layout: Object})
- * @type function
- * @alias CIQ.ChartEngine#callbacks[`layout`]
- * @memberof CIQ.ChartEngine#callbacks
- */
- layout: {
- enumerable: true,
- get: callbackGetter("layout", this),
- set: callbackSetter("layout", this)
- },
- /**
- * Called when a drawing is added or deleted (all the drawings are returned, not just the new one)
- *
- * Format:
- * callback({stx:CIQ.ChartEngine, symbol: String, symbolObject:Object, drawings: Object})
- * @type function
- * @alias CIQ.ChartEngine#callbacks[`drawing`]
- * @memberof CIQ.ChartEngine#callbacks
- */
- drawing: {
- enumerable: true,
- get: callbackGetter("drawing", this),
- set: callbackSetter("drawing", this)
- },
- /**
- * Called when a right-click is detected on a highlighted drawing.
- *
- * Format:
- * callback({stx:CIQ.ChartEngine, drawing:CIQ.Drawing})
- * @type function
- * @alias CIQ.ChartEngine#callbacks[`drawingEdit`]
- * @memberof CIQ.ChartEngine#callbacks
- * @since 6.2.0
- */
- drawingEdit: {
- enumerable: true,
- get: callbackGetter("drawingEdit", this),
- set: callbackSetter("drawingEdit", this)
- },
- /**
- * Called when preferences are changed
- *
- * Format:
- * callback({stx:CIQ.ChartEngine})
- * @type function
- * @alias CIQ.ChartEngine#callbacks[`preferences`]
- * @memberof CIQ.ChartEngine#callbacks
- */
- preferences: {
- enumerable: true,
- get: callbackGetter("preferences", this),
- set: callbackSetter("preferences", this)
- },
- /**
- * Called when a theme is changed
- *
- * Format:
- * callback({stx:CIQ.ChartEngine})
- * @type function
- * @alias CIQ.ChartEngine#callbacks[`theme`]
- * @memberof CIQ.ChartEngine#callbacks
- */
- theme: {
- enumerable: true,
- get: callbackGetter("theme", this),
- set: callbackSetter("theme", this)
- },
- /**
- * Called when the symbol is changed (when loadChart is called), added (addSeries, addStudy) or removed (removeSeries, removeStudy). Note
- * that this is not called if the symbol change occurs during an importLayout
- *
- * Format:
- * callback({stx:CIQ.ChartEngine, symbol: String, symbolObject:Object, action:["master"|"add-series"|"remove-series"})
- * @type function
- * @alias CIQ.ChartEngine#callbacks[`symbolChange`]
- * @memberof CIQ.ChartEngine#callbacks
- * @since 06-2016-21
- */
- symbolChange: {
- enumerable: true,
- get: callbackGetter("symbolChange", this),
- set: callbackSetter("symbolChange", this)
- },
- /**
- * Called when the symbol is first imported into the layout.
- *
- * Format:
- * callback({stx:CIQ.ChartEngine, symbol: String, symbolObject:Object, action:"master"})
- * @type function
- * @alias CIQ.ChartEngine#callbacks[`symbolImport`]
- * @memberof CIQ.ChartEngine#callbacks
- * @since 4.0.0
- */
- symbolImport: {
- enumerable: true,
- get: callbackGetter("symbolImport", this),
- set: callbackSetter("symbolImport", this)
- },
- /**
- * Called to determine how many decimal places the stock trades in.
- *
- * This is used for heads up display and also for the current price pointer label.
- *
- * By default it is set to {@link CIQ.calculateTradingDecimalPlaces}
- *
- * Format:
- * callback({stx:CIQ.ChartEngine, chart:CIQ.ChartEngine.Chart, symbol: String, symbolObject:Object})
- *
- * @type function
- * @alias CIQ.ChartEngine#callbacks[`calculateTradingDecimalPlaces`]
- * @memberof CIQ.ChartEngine#callbacks
- * @deprecated As of 8.0.0. Use {@link CIQ.ChartEngine.Chart#calculateTradingDecimalPlaces}.
- */
- calculateTradingDecimalPlaces: {
- enumerable: true,
- get: (function (stx) {
- return function () {
- log(
- "CIQ.ChartEngine.prototype.callbacks.calculateTradingDecimalPlaces has been deprecated. Utilize CIQ.ChartEngine.Chart.prototype.calculateTradingDecimalPlaces instead."
- );
- return getValue(
- this,
- "calculateTradingDecimalPlaces",
- stx.chart.calculateTradingDecimalPlaces
- );
- };
- })(this),
- set: (function (stx) {
- return function (func) {
- log(
- "CIQ.ChartEngine.prototype.callbacks.calculateTradingDecimalPlaces has been deprecated. Utilize CIQ.ChartEngine.Chart.prototype.calculateTradingDecimalPlaces instead."
- );
- setValue(this, "calculateTradingDecimalPlaces", func);
- stx.chart.calculateTradingDecimalPlaces = func;
- };
- })(this)
- }
- });
-
- /**
- * If true then {@link CIQ.ChartEngine#doCleanupGaps} is called so long as {@link CIQ.ChartEngine#cleanupGaps} is also set.
- * This will ensure gaps will be filled in the master data from the last tick in the chart to the date of the trade.
- *
- * **Only applicable when using streamTrade()**.
Reminder: `tick` does not fill any gaps as it is not a predictable interval.
- *
- * @type boolean
- * @default
- * @alias CIQ.ChartEngine#streamParameters[`fillGaps`]
- * @memberof CIQ.ChartEngine#streamParameters
- * @since 2016-03-11
- * @deprecated See deprecation of {@link CIQ.ChartEngine#streamTrade}. Use {@link CIQ.ChartEngine#updateChartData} instead,
- * with params.fillGaps=true or rely on cleanupGaps as default behavior.
- */
- // Guard checking existence because this is a prototype object, no redefinition allowed
- Object.defineProperty(this.streamParameters, "fillGaps", {
- enumerable: true,
- get: (function (stx) {
- return function () {
- log(
- "CIQ.ChartEngine.prototype.streamParameters.fillGaps has been deprecated. Use CIQ.ChartEngine.prototype.updateChartData instead, with params.fillGaps=true or rely on cleanupGaps as default behavior."
- );
- return getValue(this, "fillGaps", true);
- };
- })(this),
- set: (function (stx) {
- return function (val) {
- log(
- "CIQ.ChartEngine.prototype.streamParameters.fillGaps has been deprecated. Use CIQ.ChartEngine.prototype.updateChartData instead, with params.fillGaps=true or rely on cleanupGaps as default behavior."
- );
- setValue(this, "fillGaps", val);
- };
- })(this)
- });
-};
-
-// These namespaces are only available for "legacy" implementations which run in browser and use global namespaces
-if (typeof window != "undefined") {
- Object.defineProperties(window, {
- STX: {
- enumerable: true,
- get: function () {
- log("STX namespace has been deprecated. Use CIQ namespace instead.");
- return CIQ;
- }
- },
- STXChart: {
- enumerable: true,
- get: function () {
- log(
- "STXChart namespace has been deprecated. Use CIQ.ChartEngine namespace instead."
- );
- return CIQ.ChartEngine;
- }
- }
- });
-}
-
-
-
-/*
- Deprecated functions - basic
-*/
-
-/**
- * ** Deprecated. ** Use {@link CIQ.ChartEngine#attachQuoteFeed} instead.
- * Attaches a quote feed to the charting engine, which causes the chart to pull data from the
- * quote feed as needed.
- *
- * @param {object} [quoteFeed] A quote feed object.
- * @param {object} [behavior] Optional behavior object to initialize tje quote feed.
- * @param {number} [behavior.refreshInterval] Sets the frequency for `fetchUpdateData`. If
- * null or zero, `fetchUpdateData` is not called.
- * @param {function} [behavior.callback] Optional callback function called after any fetch to
- * enhance functionality. It will be called with the params object used with the fetch
- * call.
- * @param {number} [behavior.noLoadMore] If true, the chart does not attempt to load any more
- * data after the initial load.
- * @param {boolean} [behavior.loadMoreReplace] If true, then when paginating, the driver
- * replaces the master data instead of prepending. Set this if your feed can only provide
- * a full data set of varying historical lengths.
- *
- * @memberOf CIQ.ChartEngine
- * @private
- * @since
- * - 2016-12-01
- * - 8.0.0 Deprecated
- * @deprecated Use {@link CIQ.ChartEngine#attachQuoteFeed}.
- *
- * @example
- * fetchUpdateData
once per
- * second.
- * Each bar width will be determined by `WidthFactor` study parameter.
- * @param {CIQ.ChartEngine} stx A chart engine instance
- * @param {CIQ.Studies.StudyDescriptor} sd A study descriptor
- * @param {array} quotes Array of quotes
- * @memberOf CIQ.Studies
- * @deprecated use {@link CIQ.Studies.createVolumeChart}
- */
-CIQ.Studies.volUnderlay = function () {
- log(
- "CIQ.Studies.volUnderlay has been deprecated. Use CIQ.Studies.createVolumeChart instead."
- );
- if (CIQ.Studies.createVolumeChart)
- CIQ.Studies.createVolumeChart.apply(null, arguments);
-};
-
-/**
- * **Deprecated since 5.2.0. This calculation is now done in {@link CIQ.ChartEngine.AdvancedInjectable#initializeDisplay} and is no longer a separate function.**
- *
- * Method to determine the minimum and maximum points in a study panel.
- *
- * @param {CIQ.ChartEngine} stx The chart object
- * @param {CIQ.Studies.StudyDescriptor} sd The study descriptor
- * @param {array} quotes The set of quotes to evaluate
- * @memberOf CIQ.Studies
- * @deprecated Since 5.2.0. This calculation is now done in {@link CIQ.ChartEngine.AdvancedInjectable#initializeDisplay} and is no longer a separate function.
- */
-CIQ.Studies.determineMinMax = function (stx, sd, quotes) {
- log(
- "CIQ.Studies.determineMinMax is no longer supported. The calculation is done automatically elsewhere."
- );
-};
-
-/**
- * Creates the yAxis for a study panel.
- *
- * @param {CIQ.ChartEngine} stx The chart object
- * @param {CIQ.Studies.StudyDescriptor} sd The study descriptor
- * @param {array} quotes The set of quotes (representing dataSegment)
- * @param {CIQ.ChartEngine.Panel} panel A reference to the panel
- * @memberOf CIQ.Studies
- * @deprecated Since 5.2.0. yAxis is now created automatically via {@link CIQ.ChartEngine#renderYAxis}
- */
-CIQ.Studies.createYAxis = function (stx, sd, quotes, panel) {
- log(
- "CIQ.Studies.createYAxis is no longer supported. The action is done automatically elsewhere."
- );
-};
-
-/**
- * **Deprecated since 6.0.0. Use {@link CIQ.ChartEngine#drawHistogram} instead.**
- *
- * Convenience function for creating a volume style chart that supports multiple colors of volume bars.
- *
- * If borderMap (border colors) is passed in then the chart will display in a format where bars are flush against
- * one another so that there is no white space between bars. If however a borderMap is not specified then white space will be left
- * between the bars.
- * @param {CIQ.ChartEngine} stx The chart object
- * @param {CIQ.Studies.StudyDescriptor} sd The study descriptor
- * @param {object} colorMap Map of colors to arrays. Each array should contain entries for each dataSegment bar mapped to that color.
- * It should contain null values for any bar that shouldn't be drawn
- * @param {object} borderMap Map of border colors for each color. If null then no borders will be drawn.
- * @example
- * var colorMap={};
- * colorMap["#FF0000"]=[56,123,null,null,45];
- * colorMap["#00FF00"]=[null,null,12,13,null];
- *
- * var borderMap={
- * "#FF0000": "#FFFFFF",
- * "#00FF00": "#FFFFDD"
- * };
- * CIQ.Studies.volumeChart(stx, sd, colorMap, borderMap);
- * @memberOf CIQ.Studies
- * @deprecated since 6.0.0 Use {@link CIQ.ChartEngine#drawHistogram} instead.
- */
-CIQ.Studies.volumeChart = function (stx, sd, colorMap, borderMap) {
- log(
- "CIQ.Studies.volumeChart has been deprecated. Use CIQ.ChartEngine.prototype.drawHistogram instead."
- );
- // Determine min max
- var maximum = Number.MAX_VALUE * -1;
- var color, value;
- for (color in colorMap) {
- for (var c = 0; c < colorMap[color].length; c++) {
- value = colorMap[color][c];
- if (!value) continue;
- if (value > maximum) maximum = value;
- }
- }
-
- // determine calculation ratios
- var panel = stx.panels[sd.panel];
- var b = Math.floor(panel.yAxis.bottom) + 0.5;
- var t = Math.floor(panel.yAxis.top) + 0.5;
- var h = b - t;
- var candleWidth = stx.layout.candleWidth;
- var borderColor = null;
- if (!sd.parameters || !sd.parameters.displayBorder) borderMap = null;
- var offset = 0;
- if (!borderMap) offset = (candleWidth - stx.chart.tmpWidth) / 2;
- var context = sd.getContext(stx);
- context.lineWidth = 1;
- stx.startClip(sd.panel);
- for (color in colorMap) {
- if (borderMap) borderColor = borderMap[color];
- context.fillStyle = color;
- if (borderColor) context.strokeStyle = borderColor;
- context.beginPath();
- var prevTop = b + 0.5;
- var farLeft = Math.floor(stx.pixelFromBar(0, panel.chart));
- var prevRight;
- for (var i = 0; i < colorMap[color].length; i++) {
- if (stx.chart.dataSegment[i] && stx.chart.dataSegment[i].candleWidth) {
- candleWidth = stx.chart.dataSegment[i].candleWidth;
- if (!borderMap) offset = candleWidth / 4;
- } else {
- candleWidth = stx.layout.candleWidth;
- if (!borderMap) offset = (candleWidth - stx.chart.tmpWidth) / 2;
- }
- if (i === 0) {
- farLeft -= candleWidth / 2;
- prevRight = farLeft;
- }
- value = colorMap[color][i];
- if (!value) {
- prevTop = b;
- prevRight += candleWidth;
- //if(borderMap) prevRight-=0.5;
- continue;
- }
- var y = value * (h / maximum);
- var top = Math.min(Math.floor(b - h + (h - y)) + 0.5, b);
- var x0, x1;
- x0 = Math.floor(prevRight + offset);
- x1 = Math.floor(prevRight + candleWidth - offset);
- x0 = Math.max(x0, farLeft);
-
- context.moveTo(x0, b);
- context.lineTo(x1, b);
- context.lineTo(x1, top);
- context.lineTo(x0, top);
- if (borderMap) {
- if (prevTop > top || i === 0) context.lineTo(x0, prevTop); // draw down to the top of the previous bar, so that we don't overlap strokes
- } else {
- context.lineTo(x0, b);
- }
- prevTop = top;
- prevRight += candleWidth;
- //if(borderMap) prevRight-=0.5;
- }
- context.fill();
- context.strokeStyle = borderColor;
- if (borderMap && stx.layout.candleWidth >= 2) context.stroke();
- context.closePath();
- }
- stx.endClip();
-};
-
-// This namespace is only available for "legacy" implementations which run in browser and use global namespaces
-if (typeof window != "undefined") {
- Object.defineProperty(window, "STXSocial", {
- enumerable: true,
- get: function () {
- log(
- "STXSocial namespace has been deprecated. Use CIQ.Share namespace instead."
- );
- return CIQ.Share;
- }
- });
-}
-
-
-
-/*
- Deprecated functions - advanced
-*/
-
-
-
-/* global $ */
-/*
- Deprecated functions - jquery & webcomponents
-*/
-
-if (typeof $ === "function" && $.fn) {
- /**
- * **Deprecated since 8.1.0.** Use {@link CIQ.trulyVisible} instead.
- *
- * Attaches a `trulyvisible` selector to a jQuery object.
- *
- * @private
- * @deprecated Use {@link CIQ.trulyVisible}.
- * @since 8.1.0 Deprecated.
- *
- * @example
- * let visible = $(node).is(":trulyvisible");
- */
- $.fn.extend($.expr[":"], {
- trulyvisible: function (node, j, attr) {
- log(
- "Use of jQuery has been deprecated. Use CIQ.trulyVisible() to return element visibility instead of custom pseudo-selector :trulyvisible."
- );
- var parents = $(node).parents();
- parents = parents.add(node);
- for (var i = 0; i < parents.length; i++) {
- var p = $(parents[i]);
- if (p.css("opacity") === "0") return false;
- if (p.css("visibility") === "hidden") return false;
- if (p.css("height") === "0px" && p.css("overflow-y") == "hidden")
- return false;
- if (!p.is(":visible")) return false;
- }
- return true;
- }
- });
-
- /**
- * **Deprecated since 8.1.0.** Use {@link CIQ.UI.stxtap} instead.
- *
- * Attaches a `stxtap` clickable event to a jQuery object.
- *
- * @param {string|function} arg1 A CSS selector used to filter the elements that trigger the
- * event or a function that serves as the event handler.
- * @param {function} arg2 A function that serves as the event handler if `arg1` is a selector.
- *
- * @private
- * @deprecated Use {@link CIQ.UI.stxtap}.
- * @since 8.1.0 Deprecated.
- *
- * @example
- * $(node).stxtap(cb);
- */
- var stxtap = function (arg1, arg2) {
- log(
- "Use of jQuery has been deprecated. Use CIQ.UI.stxtap() to attach a listener instead of $.fn.stxtap()."
- );
- return this.each(function () {
- CIQ.installTapEvent(this /*, {stopPropagation:true}*/);
- if (typeof arg1 == "string") {
- $(this).on("stxtap", arg1, function (e) {
- arg2.call(this, e);
- });
- } else {
- $(this).on("stxtap", function (e) {
- arg1.call(this, e);
- });
- }
- });
- };
-
- /**
- * **Deprecated since 8.1.0.** Use {@link CIQ.climbUpDomTree} instead.
- *
- * Returns an ancestry list starting with the current element.
- *
- * @param {*} arg1 Unused.
- * @return {jQuery} The ancestor list in non-reversed order.
- *
- * @private
- * @deprecated Use {@link CIQ.climbUpDomTree}.
- * @since 8.1.0 Deprecated.
- *
- * @example
- * $(node).parentsAndMe(); // Returns jQuery collection of elements from current node to HTML top element.
- */
- var parentsAndMe = function (arg1) {
- log(
- "Use of jQuery has been deprecated. Use CIQ.climbUpDomTree() instead of $.fn.parentsAndMe()."
- );
- var us = $(this).parents();
- us = us.add($(this)).get().reverse();
- return us;
- };
-
- /**
- * **Deprecated since 8.1.0.** Use {@link CIQ.cqvirtual} instead.
- *
- * Creates and returns a virtual DOM of an element for faster manipulation.
- *
- * @param {*} arg1 Unused.
- * @return {jQuery} A virtual DOM cloned from the actual DOM.
- *
- * @private
- * @deprecated Use {@link CIQ.cqvirtual}.
- * @since 8.1.0 Deprecated.
- */
- var cqvirtual = function (arg1) {
- log(
- "Use of jQuery has been deprecated. Use CIQ.cqvirtual() instead of $.fn.cqvirtual()."
- );
- var virtual = this.clone(true);
- virtual.empty();
- virtual.original = this;
- return virtual;
- };
-
- /**
- * **Deprecated since 8.1.0.** Use {@link CIQ.cqrender} instead.
- *
- * Copies a virtual DOM to the actual DOM. Works in tandem with `cqvirtual`.
- *
- * @param {*} arg1 Unused.
- * @return {jQuery} The actual DOM copied from the virtual DOM.
- *
- * @private
- * @deprecated Use {@link CIQ.cqrender}.
- * @since 8.1.0 Deprecated.
- */
- var cqrender = function (arg1) {
- log(
- "Use of jQuery has been deprecated. Use CIQ.cqrender() instead of $.fn.cqrender()."
- );
- if (this[0].innerHTML == this.original[0].innerHTML) return this.original;
- this.original.children(":not(template)").remove();
- var children = this.children();
- if (children.length) {
- var newStuff = children.detach();
- this.original.append(newStuff);
- }
- return this.original;
- };
-
- /**
- * **Deprecated since 8.1.0.** Use {@link CIQ.guaranteedSize} instead.
- *
- * Returns a guaranteed width. For instance, `cq-context` or any other wrapping tag can have
- * a width of zero; if so, we need to go up the ancestry tree to get the actual width.
- *
- * @return {number} The node width.
- *
- * @private
- * @deprecated Use {@link CIQ.guaranteedSize}.
- * @since 8.1.0 Deprecated.
- *
- * @example
- * $(node).guaranteedWidth(); // Returns a width as a number.
- */
- var guaranteedWidth = function () {
- log(
- "Use of jQuery has been deprecated. Use CIQ.guaranteedSize() instead of $.fn.guaranteedWidth()."
- );
- var node = this;
- var w = node.width();
- while (!w) {
- node = node.parent();
- if (node[0].tagName === "BODY" || node[0] === window) {
- return window.innerWidth;
- }
- w = node.width();
- }
- return w;
- };
-
- /**
- * **Deprecated since 8.1.0.** Use {@link CIQ.guaranteedSize} instead.
- *
- * Returns a guaranteed height. A wrapping tag, such as `cq-context`, can have a width of
- * zero; if so, we need to go up the ancestry tree to get the actual height.
- *
- * @return {number} The node height.
- *
- * @private
- * @deprecated Use {@link CIQ.guaranteedSize}.
- * @since 8.1.0 Deprecated.
- *
- * @example
- * $(node).guaranteedHeight(); // Returns a height as a number.
- */
- var guaranteedHeight = function () {
- log(
- "Use of jQuery has been deprecated. Use CIQ.guaranteedSize() instead of $.fn.guaranteedHeight()."
- );
- var node = this;
- var h = node.height();
- while (!h) {
- node = node.parent();
- if (node[0].tagName === "BODY" || node[0] === window) {
- return window.innerHeight;
- }
- h = node.height();
- }
- return h;
- };
-
- /**
- * **Deprecated since 8.1.0.** Use {@link CIQ.removeChildIfNot} instead.
- *
- * Removes all children of a node except for the "template" tags.
- *
- * @return {jQuery} The node from which the children have been removed.
- *
- * @private
- * @deprecated Use {@link CIQ.removeChildIfNot} and pass "template" as the selector argument.
- * @since 8.1.0 Deprecated.
- */
- var emptyExceptTemplate = function () {
- log(
- "Use of jQuery has been deprecated. Use CIQ.removeChildIfNot(node, 'template') instead of $.fn.emptyExceptTemplate()."
- );
- this.children().not("template").remove();
- return this;
- };
-
- /**
- * **Deprecated since 8.1.0.** Use `node.getAttribute()` and check the result against an array
- * of falsey values instead.
- *
- * @param {string} arg1 The name of the attribute checked for its truthy value.
- * @return {boolean} true if the attribute exists and is not explicitly set to false;
- * otherwise, false.
- *
- * @private
- * @deprecated Use ["false", "0", null, undefined].indexOf(this.getAttribute("...")) == -1
- * @since 8.1.0 Deprecated.
- */
- var truthyAttr = function (arg1) {
- log(
- "Use of jQuery has been deprecated. Use ['false','0',null,undefined].indexOf(this.getAttribute('...'))==-1 instead of $.fn.truthyAttr()."
- );
- var val = this.attr(arg1);
- if (typeof val == "undefined") return false;
- if (val.toLowerCase() == "false") return false;
- if (val == "0") return false;
- return true;
- };
-
- /**
- * **Deprecated since 8.1.0.** Check attribute before setting the attribute value instead.
- *
- * Checks an attribute to see if it needs to be changed before changing it. Efficient
- * because it doesn't change the DOM unless it needs to. Note that this does not support
- * jQuery chaining.
- *
- * @param {string} attribute The attribute to be checked.
- * @param {*} value The value to apply to the attribute if the attribute does not already have
- * this value.
- *
- * @private
- * @deprecated Check attribute before setting instead.
- * @since 8.1.0 Deprecated.
- */
- var attrBetter = function (attribute, value) {
- log(
- "Use of jQuery has been deprecated. check attribute value before setting instead of $.fn.attrBetter()."
- );
- return this.attr(attribute, function (i, val) {
- if (typeof value == "undefined") value = "true";
- if (value !== val) return value;
- });
- };
-
- /**
- * **Deprecated since 8.1.0.** Check `hasAttribute` before removing the attribute instead.
- *
- * Removes an attribute if the attribute has a value. Note that this does not support jQuery
- * chaining.
- *
- * @param {string} attribute The attribute to be removed.
- * @return {boolean} true if the attribute was removed, false if the attribute did not have a
- * value.
- *
- * @private
- * @deprecated Check `hasAttribute` before removing instead.
- * @since 8.1.0 Deprecated.
- */
- var removeAttrBetter = function (attribute) {
- log(
- "Use of jQuery has been deprecated. check hasAttribute before removing instead of $.fn.removeAttrBetter()."
- );
- var val = this.attr(attribute);
- if (!val && val !== "") return false;
- this.removeAttr(attribute);
- return true;
- };
-
- /**
- * **Deprecated since 8.1.0.** Check `innerText` before setting the text instead.
- *
- * Checks `innerText` to see if it needs to be changed before changing it. Efficient because
- * it doesn't change the DOM unless it needs to. Note that this is a setter function only. It
- * is not meant to replace the getter aspect of jQuery's built in `text()` function.
- *
- * @param {string} str The text to which `innerText` is changed if `innerText` is not already
- * the same as this text.
- * @return {boolean} true if `innerText` was changed; otherwise, false.
- *
- * @private
- * @deprecated Check `innerText` before setting instead.
- * @since 8.1.0 Deprecated.
- */
- var textBetter = function (str) {
- log(
- "Use of jQuery has been deprecated. check innerText before setting instead of $.fn.textBetter()."
- );
- if (this.text() === str) return false;
- this.text(str);
- return true;
- };
-
- $.fn.extend({
- stxtap,
- parentsAndMe,
- cqvirtual,
- cqrender,
- guaranteedWidth,
- guaranteedHeight,
- emptyExceptTemplate,
- truthyAttr,
- attrBetter,
- removeAttrBetter,
- textBetter
- });
-
- /**
- * **Deprecated since 8.1.0.** Use {@link CIQ.qs} instead.
- *
- * Returns the value of a token in a page's URL.
- *
- * @param {string} sParam The token for which a value is returned.
- * @return {string} The value of token or null if the token does not exist in the URL.
- *
- * @private
- * @deprecated Use {@link CIQ.qs} instead.
- * @since 8.1.0 Deprecated.
- *
- * @example
- * // Assume the page URL is "http://127.0.0.1:8000?name=value&foo=bar..."
- * var value = $.queryString("name");
- */
- $.queryString = function (sParam) {
- log(
- "Use of jQuery has been deprecated. Use CIQ.qs() to return an object containing querystring tokens instead of $.querystring()."
- );
- var sPageURL = window.location.search.substring(1);
- var sURLVariables = sPageURL.split("&");
- for (var i = 0; i < sURLVariables.length; i++) {
- var sParameterName = sURLVariables[i].split("=");
- if (sParameterName[0] == sParam) return sParameterName[1];
- }
- return null;
- };
-}
-
-if (!CIQ.UI) CIQ.UI = {};
-
-/**
- * **Deprecated since 8.1.0.** Use `document.querySelectorAll("cq-context,*[cq-context]")` instead.
- *
- * Utility function that returns all contexts on the screen.
- *
- * Designed to be used as a helper method for the included {@link WebComponents}. A full
- * tutorial on how to work with and customize the web components can be found here:
- * {@tutorial Web Component Interface}.
- *
- * @return {jQuery} A jQuery node with all contexts.
- *
- * @memberof CIQ.UI
- * @deprecated Use `document.querySelectorAll("cq-context,*[cq-context]")`.
- * @since 8.1.0 Deprecated.
- */
-CIQ.UI.allContexts = function () {
- log(
- "CIQ.UI.allContexts has been deprecated. Use document.querySelectorAll('cq-context,*[cq-context]') instead."
- );
- return $("cq-context,*[cq-context]");
-};
-
-/**
- * Static method to create an observable.
- *
- * Designed to be used as a helper method for the included {@link WebComponents}. A full
- * tutorial on how to work with and customize the Web Components can be found here:
- * {@tutorial Web Component Interface}.
- *
- * @param {Object} params Parameters.
- * @param {String} [params.selector] The selector to effect the observable (adding class,
- * setting value).
- * @param {Object} params.obj The object to observe.
- * @param {String} [params.member] The member of the object to observe. Pass an array to
- * observe multiple members. Or pass nothing to observe any change to the object.
- * @param {String} [params.condition] Optional condition for the member to trigger the action.
- * @param {String} params.action The action to take: "class" - add or remove a class,
- * "callback" - calls back with params.
- * @param {String} params.value The value for the action (for example, class name or
- * callback function).
- * @return {Function} Handler for use when unobserving.
- *
- * @memberof CIQ.UI
- *
- * @example
- *
This object can be extended to support additional drawing tools (for instance note the extensive customization capabilities for fibonacci)
- * @type object
- * @memberof CIQ.ChartEngine
- * @static
- */
-CIQ.ChartEngine.currentVectorParameters = {
- /**
- * Drawing to activate.
- *
See 'Classes' in {@link CIQ.Drawing} for available drawings.
- * Use {@link CIQ.ChartEngine#changeVectorType} to activate.
- * @type string
- * @alias CIQ.ChartEngine.currentVectorParameters[`vectorType`]
- * @memberof CIQ.ChartEngine.currentVectorParameters
- */
- vectorType: null,
- /**
- * Line pattern.
- *
Valid values for pattern: solid, dotted, dashed, none
- *
Not all parameters/values are valid on all drawings. See the specific `reconstruct` method for your desired drawing for more details(Example: {@link CIQ.Drawing.horizontal#reconstruct})
- * @type string
- * @default
- * @alias CIQ.ChartEngine.currentVectorParameters[`pattern`]
- * @memberof CIQ.ChartEngine.currentVectorParameters
- */
- pattern: "solid",
- /**
- * Line width
- *
Not all parameters/values are valid on all drawings. See the specific `reconstruct` method for your desired drawing for more details(Example: {@link CIQ.Drawing.horizontal#reconstruct})
- * @type number
- * @default
- * @alias CIQ.ChartEngine.currentVectorParameters[`lineWidth`]
- * @memberof CIQ.ChartEngine.currentVectorParameters
- */
- lineWidth: 1,
- /**
- * Fill color.
- *
Not all parameters/values are valid on all drawings. See the specific `reconstruct` method for your desired drawing for more details(Example: {@link CIQ.Drawing.horizontal#reconstruct})
- * @type string
- * @default
- * @alias CIQ.ChartEngine.currentVectorParameters[`fillColor`]
- * @memberof CIQ.ChartEngine.currentVectorParameters
- */
- fillColor: "#7DA6F5",
- /**
- * Line color.
- *
Not all parameters/values are valid on all drawings. See the specific `reconstruct` method for your desired drawing for more details(Example: {@link CIQ.Drawing.horizontal#reconstruct})
- * @type string
- * @default
- * @alias CIQ.ChartEngine.currentVectorParameters[`currentColor`]
- * @memberof CIQ.ChartEngine.currentVectorParameters
- */
- currentColor: "auto",
- /**
- * Axis Label.
- * Set to 'true' to display a label on the x axis.
- *
Not all parameters/values are valid on all drawings. See the specific `reconstruct` method for your desired drawing for more details(Example: {@link CIQ.Drawing.horizontal#reconstruct})
- * @type string
- * @default
- * @alias CIQ.ChartEngine.currentVectorParameters[`axisLabel`]
- * @memberof CIQ.ChartEngine.currentVectorParameters
- */
- axisLabel: true,
- /**
- * Fibonacci settings.
- * See {@link CIQ.Drawing.fibonacci#reconstruct} `parameters` object for valid options
- * @type object
- * @alias CIQ.ChartEngine.currentVectorParameters[`fibonacci`]
- * @memberof CIQ.ChartEngine.currentVectorParameters
- * @example
- * fibonacci:{
- * trend:{color:"auto", parameters:{pattern:"solid", opacity:0.25, lineWidth:1}},
- * fibs:[
- * {level:-0.786, color:"auto", parameters:{pattern:"solid", opacity:0.25, lineWidth:1}},
- * {level:-0.618, color:"auto", parameters:{pattern:"solid", opacity:0.25, lineWidth:1}, display: true},
- * {level:-0.382, color:"auto", parameters:{pattern:"solid", opacity:0.25, lineWidth:1}, display: true},
- * {level:0, color:"auto", parameters:{pattern:"solid", lineWidth:1}, display: true},
- * {level:0.382, color:"auto", parameters:{pattern:"solid", opacity:0.25, lineWidth:1}, display: true},
- * {level:0.618, color:"auto", parameters:{pattern:"solid", opacity:0.25, lineWidth:1}, display: true},
- * {level:0.786, color:"auto", parameters:{pattern:"solid", opacity:0.25, lineWidth:1}},
- * {level:0.5, color:"auto", parameters:{pattern:"solid", opacity:0.25, lineWidth:1}, display: true},
- * {level:1, color:"auto", parameters:{pattern:"solid", lineWidth:1}, display: true},
- * {level:1.382, color:"auto", parameters:{pattern:"solid", opacity:0.25, lineWidth:1}, display: true},
- * {level:1.618, color:"auto", parameters:{pattern:"solid", opacity:0.25, lineWidth:1}, display: true}
- * ],
- * extendLeft: false,
- * printLevels: true, // display the % levels to the right of the drawing
- * printValues: false, // display the values on the y axis
- * timezone:{color:"auto", parameters:{pattern:"solid", opacity:0.25, lineWidth:1}}
- * }
- * @since
- * - 3.0.9 Added 0.786 and -0.786 levels.
- * - 5.2.0 Added 1.272 level.
- */
- fibonacci: {
- trend: {
- color: "auto",
- parameters: { pattern: "solid", opacity: 0.25, lineWidth: 1 }
- },
- fibs: [
- {
- level: -0.786,
- color: "auto",
- parameters: { pattern: "solid", opacity: 0.25, lineWidth: 1 }
- },
- {
- level: -0.618,
- color: "auto",
- parameters: { pattern: "solid", opacity: 0.25, lineWidth: 1 },
- display: true
- },
- {
- level: -0.5,
- color: "auto",
- parameters: { pattern: "solid", opacity: 0.25, lineWidth: 1 }
- },
- {
- level: -0.382,
- color: "auto",
- parameters: { pattern: "solid", opacity: 0.25, lineWidth: 1 },
- display: true
- },
- {
- level: -0.236,
- color: "auto",
- parameters: { pattern: "solid", opacity: 0.25, lineWidth: 1 }
- },
- {
- level: 0,
- color: "auto",
- parameters: { pattern: "solid", lineWidth: 1 },
- display: true
- },
- {
- level: 0.236,
- color: "auto",
- parameters: { pattern: "solid", opacity: 0.25, lineWidth: 1 }
- },
- {
- level: 0.382,
- color: "auto",
- parameters: { pattern: "solid", opacity: 0.25, lineWidth: 1 },
- display: true
- },
- {
- level: 0.5,
- color: "auto",
- parameters: { pattern: "solid", opacity: 0.25, lineWidth: 1 },
- display: true
- },
- {
- level: 0.618,
- color: "auto",
- parameters: { pattern: "solid", opacity: 0.25, lineWidth: 1 },
- display: true
- },
- {
- level: 0.786,
- color: "auto",
- parameters: { pattern: "solid", opacity: 0.25, lineWidth: 1 }
- },
- {
- level: 1,
- color: "auto",
- parameters: { pattern: "solid", lineWidth: 1 },
- display: true
- },
- {
- level: 1.272,
- color: "auto",
- parameters: { pattern: "solid", opacity: 0.25, lineWidth: 1 }
- },
- {
- level: 1.382,
- color: "auto",
- parameters: { pattern: "solid", opacity: 0.25, lineWidth: 1 },
- display: true
- },
- {
- level: 1.618,
- color: "auto",
- parameters: { pattern: "solid", opacity: 0.25, lineWidth: 1 },
- display: true
- },
- {
- level: 2.618,
- color: "auto",
- parameters: { pattern: "solid", opacity: 0.25, lineWidth: 1 }
- },
- {
- level: 4.236,
- color: "auto",
- parameters: { pattern: "solid", opacity: 0.25, lineWidth: 1 }
- }
- ],
- extendLeft: false,
- printLevels: true,
- printValues: false,
- timezone: {
- color: "auto",
- parameters: { pattern: "solid", opacity: 0.25, lineWidth: 1 }
- }
- },
- /**
- * Annotation settings.
- * @type object
- * @alias CIQ.ChartEngine.currentVectorParameters[`annotation`]
- * @memberof CIQ.ChartEngine.currentVectorParameters
- * @example
- * annotation:{
- * font:{
- * style:null,
- * size:null, // override .stx_annotation default
- * weight:null, // override .stx_annotation default
- * family:null // override .stx_annotation default
- * }
- * }
- */
- annotation: {
- font: {
- style: null,
- size: null, // override .stx_annotation default
- weight: null, // override .stx_annotation default
- family: null // override .stx_annotation default
- }
- }
-};
-
-/**
- * Registers a drawing tool. This is typically done using lazy eval.
- * @private
- * @param {string} name Name of drawing tool
- * @param {function} func Constructor for drawing tool
- * @memberof CIQ.ChartEngine
- */
-CIQ.ChartEngine.registerDrawingTool = function (name, func) {
- CIQ.ChartEngine.drawingTools[name] = func;
-};
-
-/**
- * Given an html element, this allows the chart container to keep track of its own drawing container
- * (where appropriate)
- * @param {object} htmlElement The html element where the chart container is for 'this' chart
- * @memberof CIQ.ChartEngine
- * @example
- * var stxx=new CIQ.ChartEngine({container:document.querySelector(".chartContainer"), preferences:{labels:false, currentPriceLine:true, whitespace:0}});
- * stxx.setDrawingContainer(document.querySelector('cq-toolbar'));
- * @since 6.0.0
- */
-CIQ.ChartEngine.prototype.setDrawingContainer = function (htmlElement) {
- this.drawingContainer = htmlElement;
-};
-
-/**
- * Exports (serializes) all of the drawings on the chart(s) so that they can be saved to an external database and later imported with {@link CIQ.ChartEngine#importDrawings}.
- * @see {@link CIQ.ChartEngine#importDrawings}
- * @return {array} An array of serialized objects representing each drawing
- * @memberof CIQ.ChartEngine
- * @since 3.0.0 Replaces `serializeDrawings`.
- */
-CIQ.ChartEngine.prototype.exportDrawings = function () {
- var arr = [];
- for (var i = 0; i < this.drawingObjects.length; i++) {
- arr.push(this.drawingObjects[i].serialize());
- }
- return arr;
-};
-
-/**
- * Causes all drawings to delete themselves. External access should be made through @see CIQ.ChartEngine.prototype.clearDrawings
- * @param {boolean} deletePermanent Set to false to not delete permanent drawings
- * @private
- * @memberof CIQ.ChartEngine
- * @since 6.0.0 Added `deletePermanent` parameter.
- */
-CIQ.ChartEngine.prototype.abortDrawings = function (deletePermanent) {
- if (deletePermanent !== false) deletePermanent = true;
- for (var i = this.drawingObjects.length - 1; i >= 0; i--) {
- var drawing = this.drawingObjects[i];
- drawing.abort(true);
- if (deletePermanent || !drawing.permanent) this.drawingObjects.splice(i, 1);
- }
-};
-
-/**
- * Imports drawings from an array originally created by {@link CIQ.ChartEngine#exportDrawings}.
- * To immediately render the reconstructed drawings, you must call `draw()`.
- * See {@tutorial Using and Customizing Drawing Tools} for more details.
- *
- * **Important:**
- * Calling this function in a way that will cause it to run simultaneously with [importLayout]{@link CIQ.ChartEngine#importLayout}
- * will damage the results on the layout load. To prevent this, use the {@link CIQ.ChartEngine#importLayout} or {@link CIQ.ChartEngine#loadChart} callback listeners.
- *
- * @see {@link CIQ.ChartEngine#exportDrawings}
- * @param {array} arr An array of serialized drawings
- * @memberof CIQ.ChartEngine
- * @since 4.0.0 Replaces `reconstructDrawings`.
- * @example
- * // programmatically add a rectangle
- * stxx.importDrawings([{"name":"rectangle","pnl":"chart","col":"transparent","fc":"#7DA6F5","ptrn":"solid","lw":1.1,"d0":"20151216030000000","d1":"20151216081000000","tzo0":300,"tzo1":300,"v0":152.5508906882591,"v1":143.3385829959514}]);
- * // programmatically add a vertical line
- * stxx.importDrawings([{"name":"vertical","pnl":"chart","col":"transparent","ptrn":"solid","lw":1.1,"v0":147.45987854251013,"d0":"20151216023000000","tzo0":300,"al":true}]);
- * // now render the reconstructed drawings
- * stxx.draw();
- */
-CIQ.ChartEngine.prototype.importDrawings = function (arr) {
- if (!CIQ.Drawing) return;
- for (var i = 0; i < arr.length; i++) {
- var rep = arr[i];
- if (rep.name == "fibonacci") rep.name = "retracement";
- var Factory = CIQ.ChartEngine.drawingTools[rep.name];
- if (!Factory) {
- if (CIQ.Drawing[rep.name]) {
- Factory = CIQ.Drawing[rep.name];
- CIQ.ChartEngine.registerDrawingTool(rep.name, Factory);
- }
- }
- if (Factory) {
- var drawing = new Factory();
- drawing.reconstruct(this, rep);
- this.drawingObjects.push(drawing);
- }
- }
-};
-
-/**
- * Clears all the drawings on the chart. (Do not call abortDrawings directly).
- * @param {boolean} cantUndo Set to true to make this an "non-undoable" operation
- * @param {boolean} deletePermanent Set to false to not delete permanent drawings
- * @memberof CIQ.ChartEngine
- * @since 6.0.0 Added `deletePermanent` parameter.
- */
-CIQ.ChartEngine.prototype.clearDrawings = function (cantUndo, deletePermanent) {
- if (deletePermanent !== false) deletePermanent = true;
- var before = this.exportDrawings();
- this.abortDrawings(deletePermanent);
- if (cantUndo) {
- this.undoStamps = [];
- } else {
- this.undoStamp(before, this.exportDrawings());
- }
- this.changeOccurred("vector");
- //this.createDataSet();
- //this.deleteHighlighted(); // this will remove any stickies and also call draw()
- // deleteHighlighted was doing too much, so next we call 'just' what we need.
- this.cancelTouchSingleClick = true;
- CIQ.clearCanvas(this.chart.tempCanvas, this);
- this.draw();
- var mSticky = this.controls.mSticky;
- if (mSticky) {
- mSticky.style.display = "none";
- mSticky.children[0].innerHTML = "";
- }
-};
-
-/**
- * Creates a new drawing of the specified type with the specified parameters. See {@tutorial Using and Customizing Drawing Tools} for more details.
- * @param {string} type Drawing name
- * @param {object} parameters Parameters that describe the drawing
- * @return {CIQ.Drawing} A drawing object
- * @memberof CIQ.ChartEngine
- */
-CIQ.ChartEngine.prototype.createDrawing = function (type, parameters) {
- if (!CIQ.Drawing) return;
- var drawing = new CIQ.Drawing[type]();
- drawing.reconstruct(this, parameters);
- //set default configs if not provided
- var config = new CIQ.Drawing[type]();
- config.stx = this;
- config.copyConfig();
- for (var prop in config) {
- drawing[prop] = drawing[prop] || config[prop];
- }
- this.drawingObjects.push(drawing);
- this.draw();
- return drawing;
-};
-
-/**
- * Removes the drawing. Drawing object should be one returned from {@link CIQ.ChartEngine#createDrawing}. See {@tutorial Using and Customizing Drawing Tools} for more details.
- * @param {object} drawing Drawing object
- * @memberof CIQ.ChartEngine
- */
-CIQ.ChartEngine.prototype.removeDrawing = function (drawing) {
- for (var i = 0; i < this.drawingObjects.length; i++) {
- if (this.drawingObjects[i] == drawing) {
- this.drawingObjects.splice(i, 1);
- this.changeOccurred("vector");
- this.draw();
- return;
- }
- }
-
- //this.checkForEmptyPanel(drawing.panelName);
-};
-
-/**
- * INJECTABLE
- *
- * Stops (aborts) the current drawing. See {@link CIQ.ChartEngine#undoLast} for an actual "undo" operation.
- * @memberof CIQ.ChartEngine.AdvancedInjectable#
- * @alias undo
- */
-CIQ.ChartEngine.prototype.undo = function () {
- if (this.runPrepend("undo", arguments)) return;
- if (this.activeDrawing) {
- this.activeDrawing.abort();
- this.activeDrawing.hidden = false;
- this.drawingSnapshot = null;
- this.activateDrawing(null);
- CIQ.clearCanvas(this.chart.tempCanvas, this);
- this.draw();
- this.controls.crossX.classList.replace(
- "stx_crosshair_drawing",
- "stx_crosshair"
- );
- this.controls.crossY.classList.replace(
- "stx_crosshair_drawing",
- "stx_crosshair"
- );
- CIQ.ChartEngine.drawingLine = false;
- }
- this.runAppend("undo", arguments);
-};
-
-/**
- * Creates an undo stamp for the chart's current drawing state and triggers a call to the [undoStampEventListener]{@link CIQ.ChartEngine~undoStampEventListener}.
- *
- * Every time a drawing is added or removed the {@link CIQ.ChartEngine#undoStamps} object is updated with a new entry containing the resulting set of drawings.
- * Using the corresponding {@link CIQ.ChartEngine#undoLast} method, you can revert back to the last state, one at a time.
- * You can also use the [undoStampEventListener]{@link CIQ.ChartEngine~undoStampEventListener} to create your own tracker to undo or redo drawings.
- * @memberof CIQ.ChartEngine
- * @param {array} before The chart's array of [serialized drawingObjects]{@link CIQ.ChartEngine#exportDrawings} before being modified.
- * @param {array} after The chart's array of [serialized drawingObjects]{@link CIQ.ChartEngine#exportDrawings} after being modified
- * @since 7.0.0 'before' and 'after' parameters must now be an array of serialized drawings instead of an array of drawingObjects. See {@link CIQ.ChartEngine#exportDrawings}.
- */
-CIQ.ChartEngine.prototype.undoStamp = function (before, after) {
- this.undoStamps.push(before);
- this.dispatch("undoStamp", {
- before: before,
- after: after,
- stx: this
- });
-};
-
-/**
- * Reverts back to the previous drawing state change.
- * **Note: by design this method only manages drawings manually added during the current session and will not remove drawings restored from
- * a previous session.** If you wish to remove all drawings use {@link CIQ.ChartEngine#clearDrawings}.
- *
- * You can also view and interact with all drawings by traversing through the {@link CIQ.ChartEngine#drawingObjects} array which includes **all** drawings displayed
- * on the chart, regardless of session. Removing a drawing from this list, will remove the drawing from the chart after a draw() operation is executed.
- * @memberof CIQ.ChartEngine
- */
-CIQ.ChartEngine.prototype.undoLast = function () {
- if (this.activeDrawing) {
- this.undo();
- } else {
- if (this.undoStamps.length) {
- this.drawingObjects = []; // drawingObjects will be repopulated by importDrawings
- this.importDrawings(this.undoStamps.pop());
- this.changeOccurred("vector");
- this.draw();
- }
- }
-};
-
-/**
- * Programmatically add a drawing
- * @param {object} drawing The drawing definition
- * @memberof CIQ.ChartEngine
- * @private
- */
-CIQ.ChartEngine.prototype.addDrawing = function (drawing) {
- var drawings = this.exportDrawings();
- this.drawingObjects.push(drawing);
- this.undoStamp(drawings, this.exportDrawings());
-};
-
-/**
- * Repositions a drawing onto the temporary canvas. Called when a user moves a drawing.
- * @param {CIQ.Drawing} drawing The drawing to reposition
- * @param {boolean} activating True when first activating "reposition", so the drawing simply gets re-rendered in the same spot but on the tempCanvas.
- * (Otherwise it would jump immediately to the location of the next click/touch).
- * @since
- * - 3.0.0
- * - 5.0.0 Added `activating` parameter.
- * @private
- */
-CIQ.ChartEngine.prototype.repositionDrawing = function (drawing, activating) {
- var panel = this.panels[drawing.panelName];
- var value = this.adjustIfNecessary(
- panel,
- this.crosshairTick,
- this.valueFromPixel(this.backOutY(CIQ.ChartEngine.crosshairY), panel)
- );
- var tempCanvas = this.chart.tempCanvas;
- CIQ.clearCanvas(tempCanvas, this);
- if (activating) {
- this.drawingSnapshot = this.exportDrawings();
- drawing.render(tempCanvas.context);
- } else {
- drawing.reposition(
- tempCanvas.context,
- drawing.repositioner,
- this.crosshairTick,
- value
- );
- if (this.drawingSnapshot)
- this.undoStamp(
- CIQ.shallowClone(this.drawingSnapshot),
- this.exportDrawings()
- );
- this.drawingSnapshot = null;
- }
- if (drawing.measure) drawing.measure();
-};
-
-/**
- * Activates or deactivates repositioning on a drawings.
- * @param {CIQ.Drawing} drawing The drawing to activate. null to deactivate the current drawing.
- * @memberOf CIQ.ChartEngine
- * @since 3.0.0
- */
-CIQ.ChartEngine.prototype.activateRepositioning = function (drawing) {
- var repositioningDrawing = (this.repositioningDrawing = drawing);
- if (drawing) {
- // Take the drawing off the main canvas and put it on the tempCanvas
- this.draw();
- this.repositionDrawing(drawing, true);
- }
- this.chart.tempCanvas.style.display = drawing ? "block" : "none";
-};
-
-/**
- * Activate a drawing. The user can then finish the drawing.
- *
- * Note: Some drawings labeled "chartsOnly" can only be activated on the chart panel.
- * @param {string} drawingTool The tool to activate. Send null to deactivate.
- * @param {CIQ.ChartEngine.Panel} [panel] The panel where to activate the tool. Defaults to the current panel.
- * @return {boolean} Returns true if the drawing was successfully activated. Returns false if unactivated or unsuccessful.
- * @memberof CIQ.ChartEngine
- * @since
- * - 3.0.0
- * - 7.0.0 `panel` defaults to the current panel.
- */
-CIQ.ChartEngine.prototype.activateDrawing = function (drawingTool, panel) {
- if (!panel) panel = this.currentPanel;
- function removeStudyOverlay(stx) {
- if (!stx.layout.studies) return;
- var study = stx.layout.studies[panel.name];
- if (study && !study.overlay) delete stx.overlays[study.name];
- }
- if (!drawingTool) {
- this.activeDrawing = null;
- this.chart.tempCanvas.style.display = "none";
- removeStudyOverlay(this);
- return false;
- }
- var Factory = CIQ.ChartEngine.drawingTools[drawingTool];
- if (!Factory) {
- if (CIQ.Drawing[drawingTool]) {
- Factory = CIQ.Drawing[drawingTool];
- CIQ.ChartEngine.registerDrawingTool(drawingTool, Factory);
- }
- }
- if (Factory) {
- this.activeDrawing = new Factory();
- this.activeDrawing.construct(this, panel);
- if (!this.charts[panel.name]) {
- if (this.activeDrawing.chartsOnly) {
- this.activeDrawing = null;
- removeStudyOverlay(this);
- return false;
- }
- }
- }
- this.chart.tempCanvas.style.display = "block";
- if (this.controls.drawOk) this.controls.drawOk.style.display = "none";
- removeStudyOverlay(this);
- return true;
-};
-
-/**
- * This is called to send a potential click event to an active drawing, if one is active.
- * @param {CIQ.ChartEngine.Panel} panel The panel in which the click occurred
- * @param {number} x The X pixel location of the click
- * @param {number} y The y pixel location of the click
- * @return {boolean} Returns true if a drawing is active and received the click
- * @memberof CIQ.ChartEngine
- */
-CIQ.ChartEngine.prototype.drawingClick = function (panel, x, y) {
- if (!CIQ.Drawing) return;
- if (!panel) return; // can be true if panel was closed in the middle of a drawing
- if (this.openDialog !== "") return; // don't register a drawing click if in modal mode
- if (!this.activeDrawing) {
- if (!this.activateDrawing(this.currentVectorParameters.vectorType, panel))
- return;
- }
- if (this.activeDrawing) {
- if (this.userPointerDown && !this.activeDrawing.dragToDraw) {
- if (!CIQ.ChartEngine.drawingLine) this.activateDrawing(null);
- return;
- }
-
- var tick = this.tickFromPixel(x, panel.chart);
- var dpanel = this.panels[this.activeDrawing.panelName];
- var value = this.adjustIfNecessary(
- dpanel,
- tick,
- this.valueFromPixel(y, dpanel)
- );
- if (this.magnetizedPrice) {
- value = this.adjustIfNecessary(dpanel, tick, this.magnetizedPrice);
- }
- if (this.activeDrawing.click(this.chart.tempCanvas.context, tick, value)) {
- if (this.activeDrawing) {
- // Just in case the drawing aborted itself, such as measure
- CIQ.ChartEngine.drawingLine = false;
- CIQ.clearCanvas(this.chart.tempCanvas, this);
- this.addDrawing(this.activeDrawing); // Save drawing
- this.activateDrawing(null);
- this.adjustDrawings();
- this.draw();
- this.changeOccurred("vector");
- this.controls.crossX.classList.replace(
- "stx_crosshair_drawing",
- "stx_crosshair"
- );
- this.controls.crossY.classList.replace(
- "stx_crosshair_drawing",
- "stx_crosshair"
- );
- }
- } else {
- this.changeOccurred("drawing");
- CIQ.ChartEngine.drawingLine = true;
- this.controls.crossX.classList.replace(
- "stx_crosshair",
- "stx_crosshair_drawing"
- );
- this.controls.crossY.classList.replace(
- "stx_crosshair",
- "stx_crosshair_drawing"
- );
- }
- return true;
- }
- return false;
-};
-
-/**
- * Dispatches a [drawingEditEventListener]{@link CIQ.ChartEngine~drawingEditEventListener} event
- * if there are any listeners. Otherwise, removes the given drawing.
- *
- * @param {CIQ.Drawing} drawing The vector instance to edit, normally provided by deleteHighlighted.
- * @param {boolean} forceEdit skip the context menu and begin editing. Used on touch devices.
- * @memberof CIQ.ChartEngine.AdvancedInjectable#
- * @alias rightClickDrawing
- * @since 6.2.0
- */
-CIQ.ChartEngine.prototype.rightClickDrawing = function (drawing, forceEdit) {
- if (this.runPrepend("rightClickDrawing", arguments)) return;
- if (drawing.permanent) return;
-
- if (this.callbackListeners.drawingEdit.length) {
- this.dispatch("drawingEdit", {
- stx: this,
- drawing: drawing,
- forceEdit: forceEdit
- });
- } else {
- var dontDeleteMe = drawing.abort();
-
- if (!dontDeleteMe) {
- var before = this.exportDrawings();
- this.removeDrawing(drawing);
- this.undoStamp(before, this.exportDrawings());
- }
-
- this.changeOccurred("vector");
- }
-
- this.runAppend("rightClickDrawing", arguments);
-};
-
-/**
- * INJECTABLE
- *
- * Calculates the magnet point for the current mouse cursor location. This is the nearest OHLC point. A small white
- * circle is drawn on the temporary canvas to indicate this location for the end user. If the user initiates a drawing then
- * the end point of the drawing will be tied to the magnet point.
- * This function is only used when creating a new drawing if CIQ.ChartEngine.preferences.magnet is true and
- * a drawing CIQ.ChartEngine.currentVectorParameters.vectorType has been enabled. It will not be used when an existing drawing is being repositioned.
- * @memberof CIQ.ChartEngine.AdvancedInjectable#
- * @alias magnetize
- */
-CIQ.ChartEngine.prototype.magnetize = function () {
- this.magnetizedPrice = null;
- if (!this.preferences.magnet) return;
- if (this.runPrepend("magnetize", arguments)) return;
- if (this.repositioningDrawing) return; // Don't magnetize
- var drawingTool = this.currentVectorParameters.vectorType;
- if (!drawingTool || drawingTool == "projection" || drawingTool == "freeform")
- return;
- if (
- (drawingTool == "annotation" || drawingTool == "callout") &&
- CIQ.ChartEngine.drawingLine
- )
- return; // Don't magnetize the end of an annotation
- var panel = this.currentPanel;
- var chart = panel.chart;
- var tick = this.crosshairTick;
- //if(this.layout.interval!="minute") tick/=this.layout.periodicity;
- if (tick > chart.dataSet.length) return; // Don't magnetize in the future
- var prices = chart.dataSet[tick];
- if (!prices) return;
- var doTransform = chart.transformFunc && panel.yAxis === chart.yAxis;
- if (doTransform && prices.transform) prices = prices.transform;
- var stickMagnet;
-
- var fields = this.getRenderedItems();
- var ohlc = ["Open", "High", "Low", "Close"];
- if (this.magneticHold && this.activeDrawing && this.activeDrawing.penDown) {
- if (ohlc.indexOf(this.magneticHold) != -1 && fields.indexOf("High") != -1)
- fields = ohlc;
- else fields = [this.magneticHold];
- } else this.magneticHold = null; //reset for next time!
- var closest = 1000000000;
- var magnetRadius = parseFloat(this.preferences.magnet); // if it is actually a number we use that otherwise magnetRadius is falsey and no harm
- for (var i = 0; i < fields.length; i++) {
- var fieldPrice = prices[fields[i]];
- var yAxis = this.getYAxisByField(panel, fields[i]);
-
- var tuple = CIQ.existsInObjectChain(prices, fields[i]);
- if (tuple) fieldPrice = tuple.obj[tuple.member];
-
- if (fieldPrice || fieldPrice === 0) {
- var pixelPosition = this.pixelFromTransformedValue(
- fieldPrice,
- panel,
- yAxis
- ); // pixel position of Price!
- if (Math.abs(this.cy - pixelPosition) < closest) {
- closest = Math.abs(this.cy - pixelPosition);
- if (magnetRadius && magnetRadius <= closest) continue;
- this.magnetizedPrice = doTransform
- ? this.valueFromPixel(pixelPosition, panel)
- : fieldPrice;
- stickMagnet = pixelPosition;
- this.magneticHold = fields[i];
- }
- }
- }
- var x = this.pixelFromTick(tick, chart);
- var y = stickMagnet;
- CIQ.clearCanvas(chart.tempCanvas, this);
- var ctx = chart.tempCanvas.context;
- ctx.beginPath();
- ctx.lineWidth = 1;
- var radius = Math.max(this.layout.candleWidth, 12) / 3;
- // Limit the radius size to 8 to prevent a large arc
- // when zooming in and increasing the candle width.
- ctx.arc(x, y, Math.min(radius, 8), 0, 2 * Math.PI, false);
- // ctx.lineWidth=2;
- ctx.fillStyle = "#398dff";
- ctx.strokeStyle = "#398dff";
- ctx.fill();
- ctx.stroke();
- ctx.closePath();
- chart.tempCanvas.style.display = "block";
- if (this.anyHighlighted) this.container.classList.remove("stx-draggable");
- if (this.activeDrawing)
- this.activeDrawing.move(ctx, this.crosshairTick, this.magnetizedPrice);
- this.runAppend("magnetize", arguments);
-};
-
-/**
- * Sets the current drawing tool as described by {@link CIQ.ChartEngine.currentVectorParameters} (segment, line, etc)
- * @param {string} value The name of the drawing tool to enable
- * @memberof CIQ.ChartEngine
- * @example
- * // activates a drawing type described by currentVectorParameters
- * stxx.changeVectorType('rectangle');
- * // deactivates drawing mode
- * stxx.changeVectorType('');
- * // clears the drawings
- * stxx.clearDrawings()
- */
-CIQ.ChartEngine.prototype.changeVectorType = function (value) {
- this.currentVectorParameters.vectorType = value;
- if (CIQ.Drawing) CIQ.Drawing.initializeSettings(this, value);
- //if(value==""){ //need to always undo here to allow release of last drawing tool
- if (CIQ.ChartEngine.drawingLine) this.undo();
- //}
- // this.setCrosshairColors();
- if (this.insideChart) {
- this.doDisplayCrosshairs();
- }
-};
-
-/**
- * Sets the current drawing parameter as described by {@link CIQ.ChartEngine.currentVectorParameters} (color, pattern, etc)
- * @param {string} parameter The name of the drawing parameter to change (currentColor, fillColor, lineWidth, pattern, axisLabel, fontSize, fontStyle, fontWeight, fontFamily)
- * @param {string} value The value of the parameter
- * @return {boolean} True if property was assigned
- * @memberof CIQ.ChartEngine
- * @example
- * this.stx.changeVectorParameter("currentColor","yellow"); // or rgb/hex
- * this.stx.changeVectorParameter("axisLabel",false); // or "false"
- * this.stx.changeVectorParameter("lineWidth",5); // or "5"
- * this.stx.changeVectorParameter("fontSize","12"); // or 12 or "12px"
- * this.stx.changeVectorParameter("pattern","dotted");
- *
- * @since 3.0.0
- */
-CIQ.ChartEngine.prototype.changeVectorParameter = function (parameter, value) {
- if (parameter == "axisLabel")
- value = value.toString() === "true" || Number(value);
- else if (parameter == "lineWidth") value = Number(value);
- else if (parameter == "fontSize") value = parseInt(value, 10) + "px";
- var currentVectorParams = this.currentVectorParameters;
- if (typeof currentVectorParams[parameter] !== "undefined") {
- currentVectorParams[parameter] = value;
- return true;
- } else if (parameter.substr(0, 4) == "font") {
- parameter = parameter.substr(4).toLowerCase();
- if (parameter == "family" && value.toLowerCase() == "default") value = null;
- currentVectorParams = currentVectorParams.annotation.font;
- if (typeof currentVectorParams[parameter] !== "undefined") {
- currentVectorParams[parameter] = value;
- return true;
- }
- }
- return false;
-};
-
-/**
- * INJECTABLE
- * Animation Loop
- *
- * Draws the drawings (vectors). Each drawing is iterated and asked to draw itself. Drawings are automatically
- * clipped by their containing panel.
- * @memberof CIQ.ChartEngine.AdvancedInjectable#
- * @alias drawVectors
- */
-CIQ.ChartEngine.prototype.drawVectors = function () {
- if (this.vectorsShowing) return;
- if (this.runPrepend("drawVectors", arguments)) return;
- this.vectorsShowing = true;
- if (!this.chart.hideDrawings && !this.highlightedDraggable) {
- var tmpPanels = {};
- // First find all the existing panels in the given set of drawings (excluding those that aren't displayed)
- var panelName, i;
- for (i = 0; i < this.drawingObjects.length; i++) {
- var drawing = this.drawingObjects[i];
- if (drawing.hidden) continue; // do not draw this on the main canvas; it's being edited on the tempCanvas
- if (this.repositioningDrawing === drawing) continue; // don't display a drawing that is currently being repositioned because it will show on the tempCanvas
- panelName = drawing.panelName;
- if (
- !this.panels[drawing.panelName] ||
- this.panels[drawing.panelName].hidden
- )
- continue; // drawing from a panel that is not enabled
- if (!tmpPanels[panelName]) {
- tmpPanels[panelName] = [];
- }
- tmpPanels[panelName].push(drawing);
- }
- // Now render all the drawings in those panels, clipping each panel
- for (panelName in tmpPanels) {
- this.startClip(panelName);
- var arr = tmpPanels[panelName];
- for (i = 0; i < arr.length; i++) {
- arr[i].render(this.chart.context);
- }
- this.endClip();
- }
- }
- this.runAppend("drawVectors", arguments);
-};
-
-/**
- * Loops through the existing drawings and asks them to adjust themselves to the chart dimensions.
- * @memberof CIQ.ChartEngine
- */
-CIQ.ChartEngine.prototype.adjustDrawings = function () {
- for (var i = 0; i < this.drawingObjects.length; i++) {
- var drawing = this.drawingObjects[i];
- if (this.panels[drawing.panelName]) drawing.adjust();
- }
-};
-
-/**
- * Base class for Drawing Tools. Use {@link CIQ.inheritsFrom} to build a subclass for custom drawing tools.
- * The name of the subclass should be CIQ.Drawing.yourname. Whenever CIQ.ChartEngine.currentVectorParameters.vectorType==yourname, then
- * your drawing tool will be the one that is enabled when the user begins a drawing. Capitalization of yourname
- * must be an exact match otherwise the kernel will not be able to find your drawing tool.
- *
- * Each of the CIQ.Drawing prototype functions may be overridden. To create a functioning drawing tool
- * you must override the functions below that create alerts.
- *
- * Drawing clicks are always delivered in *adjusted price*. That is, if a stock has experienced splits then
- * the drawing will not display correctly on an unadjusted price chart unless this is considered during the rendering
- * process. Follow the templates to assure correct rendering under both circumstances.
- *
- * If no color is specified when building a drawing then color will be set to "auto" and the chart will automatically display
- * white or black depending on the background.
- *
- * **Permanent drawings:**
- * To make a particular drawing permanent, set its `permanent` property to `true` once created.
- *
Example:
- * ```drawingObject.permanent=true;```
- *
- * See {@tutorial Using and Customizing Drawing Tools} for more details.
- *
- * @name CIQ.Drawing
- * @constructor
- */
-CIQ.Drawing =
- CIQ.Drawing ||
- function () {
- this.chartsOnly = false; // Set this to true to restrict drawing to panels containing charts (as opposed to studies)
- this.penDown = false; // Set to true when in the midst of creating the object
- };
-
-/**
- * Since not all drawings have the same configuration parameters,
- * this is a helper function intended to return the relevant drawing parameters and default settings for the requested drawing type.
- *
- * For example, you can use the returning object as your template for creating the proper UI tool box for that particular drawing.
- * Will you need a line width UI element, a fill color?, etc. Or you can use it to determine what values you should be setting if enabling
- * a particular drawing type programmatically with specific properties.
- * @param {CIQ.ChartEngine} stx Chart object
- * @param {string} drawingName Name of drawing, e.g. "ray", "segment"
- * @returns {object} Map of parameters used in the drawing type, with their current values
- * @memberOf CIQ.Drawing
- * @since 3.0.0
- */
-CIQ.Drawing.getDrawingParameters = function (stx, drawingName) {
- var drawing;
- try {
- drawing = new CIQ.Drawing[drawingName]();
- } catch (e) {}
- if (!drawing) return null;
- drawing.stx = stx;
- drawing.copyConfig(true);
- var result = {};
- var confs = drawing.configs;
- for (var c = 0; c < confs.length; c++) {
- result[confs[c]] = drawing[confs[c]];
- }
- var style = stx.canvasStyle("stx_annotation");
- if (style && result.font) {
- result.font.size = style.fontSize;
- result.font.family = style.fontFamily;
- result.font.style = style.fontStyle;
- result.font.weight = style.fontWeight;
- }
- return result;
-};
-
-/**
- * Static method for saving drawing parameters to preferences.
- *
- * Values are stored in `stxx.preferences.drawings` and can be saved together with the rest of the chart preferences,
- * which by default are placed in the browser's local storage under "myChartPreferences".
- * @param {CIQ.ChartEngine} stx Chart object
- * @param {string} toolName Name of drawing tool, e.g. "ray", "segment", "fibonacci"
- * @memberOf CIQ.Drawing
- * @since 6.0.0
- */
-CIQ.Drawing.saveConfig = function (stx, toolName) {
- if (!toolName) return;
- var preferences = stx.preferences;
- if (!preferences.drawings) preferences.drawings = {};
- preferences.drawings[toolName] = {};
- var tempDrawing = new CIQ.Drawing[toolName]();
- tempDrawing.stx = stx;
- CIQ.Drawing.copyConfig(tempDrawing);
- tempDrawing.configs.forEach(function (config) {
- preferences.drawings[toolName][config] = tempDrawing[config];
- });
- stx.changeOccurred("preferences");
-};
-
-/**
- * Static method for restoring default drawing parameters, and removing custom preferences.
- *
- * @param {CIQ.ChartEngine} stx Chart object
- * @param {string} toolName Name of active drawing tool, e.g. "ray", "segment", "fibonacci"
- * @param {boolean} all True to restore default for all drawing objects. Otherwise only the active drawing object's defaults are restored.
- * @memberOf CIQ.Drawing
- * @since 6.0.0
- */
-CIQ.Drawing.restoreDefaultConfig = function (stx, toolName, all) {
- if (all) stx.preferences.drawings = null;
- else stx.preferences.drawings[toolName] = null;
- stx.changeOccurred("preferences");
- stx.currentVectorParameters = CIQ.clone(
- CIQ.ChartEngine.currentVectorParameters
- );
- stx.currentVectorParameters.vectorType = toolName;
-};
-
-/**
- * Static method to call optional initializeSettings instance method of the drawing whose name is passed in as an argument.
- * @param {CIQ.ChartEngine} stx Chart object
- * @param {string} drawingName Name of drawing, e.g. "ray", "segment", "fibonacci"
- * @memberOf CIQ.Drawing
- * @since 5.2.0 Calls optional instance function instead of doing all the work internally.
- */
-CIQ.Drawing.initializeSettings = function (stx, drawingName) {
- var drawing = CIQ.Drawing[drawingName];
- if (drawing) {
- var drawInstance = new drawing();
- if (drawInstance.initializeSettings) drawInstance.initializeSettings(stx);
- }
-};
-
-/**
- * Updates the drawing's field or panelName property to the passed in argument if the field of the drawing is "sourced" from the passed in name.
- *
- * This is used when moving a series or study, and there is a drawing based upon it.
- * It will be called based on the following occurrences:
- * - Panel of series changed
- * - Panel of study changed
- * - Default panel of study changed due to field change
- * - Outputs of study changed due to field change
- * - Outputs of study changed due to name change (due to field of field change)
- * @param {CIQ.ChartEngine} stx Chart object
- * @param {string} name Name of study or symbol of series to match with
- * @param {string} newName Name of new field to use for the drawing field if a name match is found
- * @param {string} newPanel Name of new panel to use for the drawing if a name match is found, ignored if `newName`` is set
- * @memberOf CIQ.Drawing
- * @since 7.0.0
- */
-CIQ.Drawing.updateSource = function (stx, name, newName, newPanel) {
- if (!name) return;
- var vectorChange = false;
- for (var dKey in stx.drawingObjects) {
- var drawing = stx.drawingObjects[dKey];
- if (!drawing.field) continue;
- if (newName) {
- // field change
- if (drawing.field == name) {
- drawing.field = newName;
- vectorChange = true;
- } else if (
- drawing.field.indexOf(name) > -1 &&
- drawing.field.indexOf(name + "-") == -1
- ) {
- drawing.field = drawing.field.replace(name, newName);
- vectorChange = true;
- }
- } else {
- // panel change
- if (drawing.field.split("-->")[0] == name || drawing.panelName == name) {
- drawing.panelName = newPanel;
- vectorChange = true;
- }
- }
- }
- if (vectorChange) stx.changeOccurred("vector");
-};
-
-/**
- * Instance function used to copy the relevant drawing parameters into itself.
- * It just calls the static function.
- * @param {boolean} withPreferences set to true to return previously saved preferences
- * @memberOf CIQ.Drawing
- * @since 3.0.0
- */
-CIQ.Drawing.prototype.copyConfig = function (withPreferences) {
- CIQ.Drawing.copyConfig(this, withPreferences);
-};
-/**
- * Static function used to copy the relevant drawing parameters into the drawing instance.
- * Use this when overriding the Instance function, to perform basic copy before performing custom operations.
- * @param {CIQ.Drawing} drawingInstance to copy into
- * @param {boolean} withPreferences set to true to return previously saved preferences
- * @memberOf CIQ.Drawing
- * @since
- * - 3.0.0
- * - 6.0.0 Overwrites parameters with those stored in `preferences.drawings`.
- */
-CIQ.Drawing.copyConfig = function (drawingInstance, withPreferences) {
- var cvp = drawingInstance.stx.currentVectorParameters;
- var configs = drawingInstance.configs;
- var c, conf;
- for (c = 0; c < configs.length; c++) {
- conf = configs[c];
- if (conf == "color") {
- drawingInstance.color = cvp.currentColor;
- } else if (conf == "parameters") {
- drawingInstance.parameters = CIQ.clone(cvp.fibonacci);
- } else if (conf == "font") {
- drawingInstance.font = CIQ.clone(cvp.annotation.font);
- } else {
- drawingInstance[conf] = cvp[conf];
- }
- }
- if (!withPreferences) return;
- var customPrefs = drawingInstance.stx.preferences;
- if (customPrefs && customPrefs.drawings) {
- CIQ.extend(drawingInstance, customPrefs.drawings[cvp.vectorType]);
- for (c = 0; c < configs.length; c++) {
- conf = configs[c];
- if (conf == "color") {
- cvp.currentColor = drawingInstance.color;
- } else if (conf == "parameters") {
- cvp.fibonacci = CIQ.clone(drawingInstance.parameters);
- } else if (conf == "font") {
- cvp.annotation.font = CIQ.clone(drawingInstance.font);
- } else {
- cvp[conf] = drawingInstance[conf];
- }
- }
- }
-};
-
-/**
- * Used to set the user behavior for creating drawings.
- *
- * By default, a drawing is created with this sequence:
- *
`move crosshair to staring point` → `click` → `move crosshair to ending point` → `click`.
- * > On a touch device this would be:
- * >
`move crosshair to staring point` → `tap` → `move crosshair to ending point` → `tap`.
- *
- * Set dragToDraw to `true` to create the drawing with the following alternate sequence:
- *
`move crosshair to staring point` → `mousedown` → `drag` → `mouseup`
- * > On a touch device this would be:
- * >
`move crosshair to staring point` → `press` → `drag` → `release`.
- *
- * This parameter is **not compatible** with drawings requiring more than one drag movement to complete, such as:
- * - Channel
- * - Continues Line
- * - Elliott Wave
- * - Gartley
- * - Pitchfork
- * - Fibonacci Projection
- *
- * Line and Ray have their own separate parameter, which also needs to be set in the same way, if this option is desired: `CIQ.Drawing.line.prototype.dragToDraw=true;`
- *
- * This parameter may be set for all drawings compatible with it, for a specific drawing type, or for a specific drawing instance. See examples.
- * @memberOf CIQ.Drawing
- * @example
- * // set drawing instance to dragToDraw. Only this one drawing will be affected
- * drawing.dragToDraw=true;
- * // Set particular drawing prototype to dragToDraw. All drawings to type "difference" will be affected
- * CIQ.Drawing["difference"].prototype.dragToDraw=true;
- * // Set all drawings to dragToDraw
- * CIQ.Drawing.prototype.dragToDraw=true;
- */
-CIQ.Drawing.prototype.dragToDraw = false;
-
-/**
- * Set this to true to disable selection, repositioning and deletion by the end user.
- *
- * This parameter may be set for all drawings, for a specific drawing type, or for a specific drawing instance. See examples.
- * @memberOf CIQ.Drawing
- * @example
- * // set drawing instance to permanent. Only this one drawing will be affected
- * drawing.permanent=true;
- * // Set particular drawing prototype to permanent. All drawings to type "difference" will be affected
- * CIQ.Drawing["difference"].prototype.permanent=true;
- * // Set all drawings to permanent
- * CIQ.Drawing.prototype.permanent=true;
- */
-CIQ.Drawing.prototype.permanent = false;
-
-/**
- * Set this to true to restrict drawing from being rendered on a study panel.
- *
- * This parameter may be set for all drawings, for a specific drawing type, or for a specific drawing instance. See examples.
- * @memberOf CIQ.Drawing
- * @example
- * // set drawing instance to chartsOnly. Only this one drawing will be affected
- * drawing.chartsOnly=true;
- * // Set particular drawing prototype to chartsOnly. All drawings to type "difference" will be affected
- * CIQ.Drawing["difference"].prototype.chartsOnly=true;
- * // Set all drawings to chartsOnly
- * CIQ.Drawing.prototype.chartsOnly=true;
- */
-CIQ.Drawing.prototype.chartsOnly = false;
-
-/**
- * Is called to tell a drawing to abort itself. It should clean up any rendered objects such as DOM elements or toggle states. It
- * does not need to clean up anything that it drew on the canvas.
- * @param {boolean} forceClear Indicates that the user explicitly has deleted the drawing (advanced usage)
- * @memberOf CIQ.Drawing
- */
-CIQ.Drawing.prototype.abort = function (forceClear) {};
-
-/**
- * Should call this.stx.setMeasure() with the measurements of the drawing if supported
- * @memberOf CIQ.Drawing
- */
-CIQ.Drawing.prototype.measure = function () {};
-
-/**
- * Initializes the drawing
- * @param {CIQ.ChartEngine} stx The chart object
- * @param {CIQ.ChartEngine.Panel} panel The panel reference
- * @memberOf CIQ.Drawing
- */
-CIQ.Drawing.prototype.construct = function (stx, panel) {
- this.stx = stx;
- this.panelName = panel.name;
-};
-
-/**
- * Called to render the drawing
- * @param {CanvasRenderingContext2D} context Canvas context on which to render.
- * @memberOf CIQ.Drawing
- */
-CIQ.Drawing.prototype.render = function (context) {
- console.warn("must implement render function!");
-};
-
-/**
- * Called when a user clicks while drawing.
- *
- * @param {object} context The canvas context.
- * @param {number} tick The tick in the `dataSet`.
- * @param {number} value The value (price) of the click.
- * @return {boolean} True if the drawing is complete. Otherwise the kernel continues accepting
- * clicks.
- *
- * @memberof CIQ.Drawing
- */
-CIQ.Drawing.prototype.click = function (context, tick, value) {
- console.warn("must implement click function!");
-};
-
-/**
- * Called when the user moves while creating a drawing.
- * @param {CanvasRenderingContext2D} context Canvas context on which to render.
- * @param {number} tick Tick in the `dataSet`.
- * @param {number} value Value at position.
- * @memberOf CIQ.Drawing
- */
-CIQ.Drawing.prototype.move = function (context, tick, value) {
- console.warn("must implement move function!");
-};
-
-/**
- * Called when the user attempts to reposition a drawing. The repositioner is the object provided
- * by {@link CIQ.Drawing.intersected} and can be used to determine which aspect of the drawing is
- * being repositioned. For instance, this object may indicate which point on the drawing was
- * selected by the user. It might also contain the original coordinates of the point or anything
- * else that is useful to render the drawing.
- *
- * @param {object} context The canvas context.
- * @param {object} repositioner The repositioner object.
- * @param {number} tick Current tick in the `dataSet` for the mouse cursor.
- * @param {number} value Current value in the `dataSet` for the mouse cursor.
- *
- * @memberof CIQ.Drawing
- */
-CIQ.Drawing.prototype.reposition = function (
- context,
- repositioner,
- tick,
- value
-) {};
-/**
- * Called to determine whether the drawing is intersected by either the tick/value (pointer
- * location) or box (small box surrounding the pointer). For line-based drawings, the box should
- * be checked. For area drawings (rectangles, circles) the point should be checked.
- *
- * @param {number} tick The tick in the `dataSet` representing the cursor point.
- * @param {number} value The value (price) representing the cursor point.
- * @param {object} box x0, y0, x1, y1, r representing an area around the cursor, including radius.
- * @return {object} An object that contains information about the intersection. This object is
- * passed back to {@link CIQ.Drawing.reposition} when repositioning the drawing. Return
- * false or null if not intersected. Simply returning true highlights the drawing.
- *
- * @memberof CIQ.Drawing
- */
-CIQ.Drawing.prototype.intersected = function (tick, value, box) {
- console.warn("must implement intersected function!");
-};
-
-/**
- * Reconstruct this drawing type from a serialization object
- * @param {CIQ.ChartEngine} stx Instance of the chart engine
- * @param {object} obj Serialized data about the drawing from which it can be reconstructed.
- * @memberOf CIQ.Drawing
- */
-CIQ.Drawing.prototype.reconstruct = function (stx, obj) {
- console.warn("must implement reconstruct function!");
-};
-
-/**
- * Serialize a drawing into an object.
- * @memberOf CIQ.Drawing
- */
-CIQ.Drawing.prototype.serialize = function () {
- console.warn("must implement serialize function!");
-};
-
-/**
- * Called whenever periodicity changes so that drawings can adjust their rendering.
- * @memberOf CIQ.Drawing
- */
-CIQ.Drawing.prototype.adjust = function () {
- console.warn("must implement adjust function!");
-};
-
-/**
- * Returns the highlighted state. Set this.highlighted to the highlight state.
- * For simple drawings the highlighted state is just true or false. For complex drawings
- * with pivot points for instance, the highlighted state may have more than two states.
- * Whenever the highlighted state changes a draw() event will be triggered.
- * @param {Boolean} highlighted True to highlight the drawing, false to unhighlight
- * @memberOf CIQ.Drawing.BaseTwoPoint
- */
-CIQ.Drawing.prototype.highlight = function (highlighted) {
- if (highlighted && !this.highlighted) {
- this.highlighted = highlighted;
- } else if (!highlighted && this.highlighted) {
- this.highlighted = highlighted;
- }
- return this.highlighted;
-};
-
-CIQ.Drawing.prototype.littleCircleRadius = function () {
- var radius = 6; //Math.max(12, this.layout.candleWidth)/2;
- return radius;
-};
-
-CIQ.Drawing.prototype.littleCircle = function (ctx, x, y, fill) {
- if (this.permanent) return;
- var strokeColor = this.stx.defaultColor;
- var fillColor = CIQ.chooseForegroundColor(strokeColor);
- ctx.beginPath();
- ctx.lineWidth = 1;
- ctx.arc(x, y, this.littleCircleRadius(), 0, 2 * Math.PI, false);
- if (fill) ctx.fillStyle = strokeColor;
- else ctx.fillStyle = fillColor;
- ctx.strokeStyle = strokeColor;
- ctx.setLineDash([]);
- ctx.fill();
- ctx.stroke();
- ctx.closePath();
-};
-
-CIQ.Drawing.prototype.rotator = function (ctx, x, y, on) {
- if (this.permanent) return;
- var circleSize = this.littleCircleRadius();
- var strokeColor = this.stx.defaultColor;
- ctx.beginPath();
- ctx.lineWidth = 2;
- if (!on) ctx.globalAlpha = 0.5;
- var radius = 4 + circleSize;
- ctx.arc(x, y, radius, 0, (3 * Math.PI) / 2, false);
- ctx.moveTo(x + 2 + radius, y + 2);
- ctx.lineTo(x + radius, y);
- ctx.lineTo(x - 2 + radius, y + 2);
- ctx.moveTo(x - 2, y + 2 - radius);
- ctx.lineTo(x, y - radius);
- ctx.lineTo(x - 2, y - 2 - radius);
- ctx.strokeStyle = strokeColor;
- ctx.stroke();
- ctx.closePath();
- ctx.globalAlpha = 1;
-};
-
-CIQ.Drawing.prototype.mover = function (ctx, x, y, on) {
- if (this.permanent) return;
- var circleSize = this.littleCircleRadius();
- var strokeColor = this.stx.defaultColor;
- var length = 5;
- var start = circleSize + 1;
- ctx.save();
- ctx.lineWidth = 2;
- ctx.strokeStyle = strokeColor;
- ctx.translate(x, y);
- if (!on) ctx.globalAlpha = 0.5;
- for (var i = 0; i < 4; i++) {
- ctx.rotate(Math.PI / 2);
- ctx.beginPath();
- ctx.moveTo(0, start);
- ctx.lineTo(0, start + length);
- ctx.moveTo(-2, start + length - 2);
- ctx.lineTo(0, start + length);
- ctx.lineTo(2, start + length - 2);
- ctx.closePath();
- ctx.stroke();
- }
- ctx.globalAlpha = 1;
- ctx.restore();
-};
-
-CIQ.Drawing.prototype.resizer = function (ctx, x, y, on) {
- if (this.permanent) return;
- var circleSize = this.littleCircleRadius();
- var strokeColor = this.stx.defaultColor;
- var length = 5 * Math.sqrt(2);
- var start = circleSize + 1;
- ctx.save();
- ctx.lineWidth = 2;
- ctx.strokeStyle = strokeColor;
- ctx.translate(x, y);
- ctx.rotate(((-(x * y) / Math.abs(x * y)) * Math.PI) / 4);
- if (!on) ctx.globalAlpha = 0.5;
- for (var i = 0; i < 2; i++) {
- ctx.rotate(Math.PI);
- ctx.beginPath();
- ctx.moveTo(0, start);
- ctx.lineTo(0, start + length);
- ctx.moveTo(-2, start + length - 2);
- ctx.lineTo(0, start + length);
- ctx.lineTo(2, start + length - 2);
- ctx.closePath();
- ctx.stroke();
- }
- ctx.globalAlpha = 1;
- ctx.restore();
-};
-
-/**
- * Returns true if the tick and value are inside the box
- * @param {number} tick The tick
- * @param {number} value The value
- * @param {object} box The box
- * @param {boolean} isPixels True if tick and value are in pixels; otherwise, they assumed to be in ticks and untransformed y-axis values, respectively
- * @return {boolean} True if the tick and value are within the box
- * @memberOf CIQ.Drawing
- * @since 7.0.0 Added `isPixels`.
- */
-CIQ.Drawing.prototype.pointIntersection = function (
- tick,
- value,
- box,
- isPixels
-) {
- var panel = this.stx.panels[this.panelName];
- if (!panel) return false;
- if (isPixels) {
- if (
- tick >= box.cx0 &&
- tick <= box.cx1 &&
- value >= box.cy0 &&
- value <= box.cy1
- )
- return true;
- } else {
- if (
- tick >= box.x0 &&
- tick <= box.x1 &&
- value >= Math.min(box.y0, box.y1) &&
- value <= Math.max(box.y0, box.y1)
- )
- return true;
- }
- return false;
-};
-
-/**
- * Sets the internal properties of the drawing points where x is a tick or a date and y is a value.
- * @param {number} point index to point to be converted (0,1)
- * @param {number|string} x index of bar in dataSet (tick) or date of tick (string form)
- * @param {number} y price
- * @param {CIQ.ChartEngine.Chart} [chart] Optional chart object
- * @memberOf CIQ.Drawing.BaseTwoPoint
- * @since 04-2015
- */
-CIQ.Drawing.prototype.setPoint = function (point, x, y, chart) {
- var tick = null;
- var date = null;
- if (typeof x == "number") tick = x;
- else if (x.length >= 8) date = x;
- else tick = Number(x);
-
- if (y || y === 0) this["v" + point] = y;
- var d;
- if (tick !== null) {
- d = this.stx.dateFromTick(tick, chart, true);
- this["tzo" + point] = d.getTimezoneOffset();
- this["d" + point] = CIQ.yyyymmddhhmmssmmm(d);
- this["p" + point] = [tick, y];
- } else if (date !== null) {
- d = CIQ.strToDateTime(date);
- if (!this["tzo" + point] && this["tzo" + point] !== 0)
- this["tzo" + point] = d.getTimezoneOffset();
- this["d" + point] = date;
- var adj = this["tzo" + point] - d.getTimezoneOffset();
- d.setMinutes(d.getMinutes() + adj);
- var forward = false;
- // if no match, we advance on intraday when there is a no time portion
- // except for free form which already handles time placement internally
- if (
- this.name != "freeform" &&
- !CIQ.ChartEngine.isDailyInterval(this.stx.layout.interval) &&
- !d.getHours() &&
- !d.getMinutes() &&
- !d.getSeconds() &&
- !d.getMilliseconds()
- )
- forward = true;
-
- this["p" + point] = [
- this.stx.tickFromDate(CIQ.yyyymmddhhmmssmmm(d), chart, null, forward),
- y
- ];
- }
-};
-
-/**
- * Compute the proper color to use when rendering lines in the drawing.
- *
- * Will use the color but if set to auto or transparent, will use the container's defaultColor.
- * However, if color is set to auto and the drawing is based off a series or study plot,
- * this function will return that plot's color.
- * If drawing is highlighted will use the highlight color as defined in stx_highlight_vector style.
- * @param {string} color Color string to check and use as a basis for setting. If not supplied, uses this.color.
- * @return {string} Color to use for the line drawing
- * @memberOf CIQ.Drawing
- * @since 7.0.0 Replaces `setLineColor`. Will return source line's color if auto.
- * @example
- * var trendLineColor=this.getLineColor();
- * this.stx.plotLine(x0, x1, y0, y1, trendLineColor, "segment", context, panel, parameters);
- */
-CIQ.Drawing.prototype.getLineColor = function (color) {
- if (!color) color = this.color;
- var stx = this.stx,
- lineColor = color;
- if (this.highlighted) {
- lineColor = stx.getCanvasColor("stx_highlight_vector");
- } else if (CIQ.isTransparent(lineColor)) {
- lineColor = stx.defaultColor;
- } else if (lineColor == "auto") {
- lineColor = stx.defaultColor;
- if (this.field) {
- // ugh, need to search for it
- var n;
- for (n in stx.layout.studies) {
- var s = stx.layout.studies[n];
- var candidateColor = s.outputs[s.outputMap[this.field]];
- if (candidateColor) {
- lineColor = candidateColor.color || candidateColor;
- break;
- }
- }
- var fallBackOn;
- for (n in stx.chart.seriesRenderers) {
- var renderer = stx.chart.seriesRenderers[n];
- for (var m = 0; m < renderer.seriesParams.length; m++) {
- var series = renderer.seriesParams[m];
- var fullField = series.field;
- if (!fullField && !renderer.highLowBars)
- fullField = this.defaultPlotField || "Close";
- if (series.symbol && series.subField)
- fullField += "-->" + series.subField;
- if (this.field == fullField) {
- lineColor = series.color;
- break;
- }
- if (series.field && series.field == this.field.split("-->")[0])
- fallBackOn = series.color;
- }
- }
- if (fallBackOn) lineColor = fallBackOn;
- }
- }
- if (lineColor == "auto") lineColor = stx.defaultColor;
-
- return lineColor;
-};
-
-/**
- * Base class for drawings that require two mouse clicks. Override as required.
- * @constructor
- * @name CIQ.Drawing.BaseTwoPoint
- */
-CIQ.Drawing.BaseTwoPoint = function () {
- this.p0 = null;
- this.p1 = null;
- this.color = "";
-};
-
-CIQ.inheritsFrom(CIQ.Drawing.BaseTwoPoint, CIQ.Drawing);
-
-CIQ.Drawing.BaseTwoPoint.prototype.configs = [];
-
-/**
- * Intersection is based on a hypothetical box that follows a user's mouse or finger. An
- * intersection occurs when the box crosses over the drawing. The type should be "segment", "ray"
- * or "line" depending on whether the drawing extends infinitely in any or both directions. Radius
- * determines the size of the box in pixels and is determined by the kernel depending on the user
- * interface (mouse, touch, etc.).
- *
- * @param {number} tick Tick in the `dataSet`.
- * @param {number} value Value at the cursor position.
- * @param {object} box x0, y0, x1, y1, r representing an area around the cursor, including the
- * radius.
- * @param {string} type Determines how the line should be treated (as segment, ray, or line) when
- * finding an intersection.
- * @param {number[]} [p0] The x/y coordinates of the first endpoint of the line that is tested for
- * intersection with `box`.
- * @param {number[]} [p1] The x/y coordinates of the second endpoint of the line that is tested for
- * intersection with `box`.
- * @param {boolean} [isPixels] Indicates that box values are in pixel values.
- * @return {boolean} True if the line intersects the box; otherwise, false.
- *
- * @memberOf CIQ.Drawing.BaseTwoPoint
- */
-CIQ.Drawing.BaseTwoPoint.prototype.lineIntersection = function (
- tick,
- value,
- box,
- type,
- p0,
- p1,
- isPixels
-) {
- if (!p0) p0 = this.p0;
- if (!p1) p1 = this.p1;
- var stx = this.stx;
- if (!(p0 && p1)) return false;
- var pixelBox = CIQ.convertBoxToPixels(stx, this.panelName, box);
- if (pixelBox.x0 === undefined) return false;
- var pixelPoint = { x0: p0[0], x1: p1[0], y0: p0[1], y1: p1[1] };
- if (!isPixels)
- pixelPoint = CIQ.convertBoxToPixels(stx, this.panelName, pixelPoint);
- return CIQ.boxIntersects(
- pixelBox.x0,
- pixelBox.y0,
- pixelBox.x1,
- pixelBox.y1,
- pixelPoint.x0,
- pixelPoint.y0,
- pixelPoint.x1,
- pixelPoint.y1,
- type
- );
-};
-
-/**
- * Determine whether the tick/value lies within the theoretical box outlined by this drawing's two
- * points.
- *
- * @param {number} tick Tick in the `dataSet`.
- * @param {number} value Value at position.
- * @param {object} box x0, y0, x1, y1, r representing an area around the cursor, including the
- * radius.
- * @return {boolean} True if box intersects the drawing.
- *
- * @memberof CIQ.Drawing.BaseTwoPoint
- */
-CIQ.Drawing.BaseTwoPoint.prototype.boxIntersection = function (
- tick,
- value,
- box
-) {
- if (!this.p0 || !this.p1) return false;
- if (
- box.x0 > Math.max(this.p0[0], this.p1[0]) ||
- box.x1 < Math.min(this.p0[0], this.p1[0])
- )
- return false;
- if (
- box.y1 > Math.max(this.p0[1], this.p1[1]) ||
- box.y0 < Math.min(this.p0[1], this.p1[1])
- )
- return false;
- return true;
-};
-
-/**
- * Any two-point drawing that results in a drawing that is less than 10 pixels
- * can safely be assumed to be an accidental click. Such drawings are so small
- * that they are difficult to highlight and delete, so we won't allow them.
- *
- * Note: it is very important to use pixelFromValueAdjusted() rather than pixelFromPrice(). This will
- * ensure that saved drawings always render correctly when a chart is adjusted or transformed for display
- * @param {number} tick Tick in the `dataSet`.
- * @param {number} value Value at position.
- * @memberOf CIQ.Drawing.BaseTwoPoint
- */
-CIQ.Drawing.BaseTwoPoint.prototype.accidentalClick = function (tick, value) {
- var panel = this.stx.panels[this.panelName];
- var x0 = this.stx.pixelFromTick(this.p0[0], panel.chart);
- var x1 = this.stx.pixelFromTick(tick, panel.chart);
- var y0 = this.stx.pixelFromValueAdjusted(panel, this.p0[0], this.p0[1]);
- var y1 = this.stx.pixelFromValueAdjusted(panel, tick, value);
- var h = Math.abs(x1 - x0);
- var v = Math.abs(y1 - y0);
- var length = Math.sqrt(h * h + v * v);
- if (length < 10) {
- this.penDown = false;
- if (this.dragToDraw) this.stx.undo();
- return true;
- }
-};
-
-/**
- * Value will be the actual underlying, unadjusted value for the drawing. Any adjustments or transformations
- * are reversed out by the kernel. Internally, drawings should store their raw data (date and value) so that
- * they can be rendered on charts with different layouts, axis, etc
- * @param {CanvasRenderingContext2D} context Canvas context on which to render.
- * @param {number} tick Tick in the `dataSet`.
- * @param {number} value Value at position.
- * @memberOf CIQ.Drawing.BaseTwoPoint
- */
-CIQ.Drawing.BaseTwoPoint.prototype.click = function (context, tick, value) {
- this.copyConfig();
- var panel = this.stx.panels[this.panelName];
- if (!this.penDown) {
- this.setPoint(0, tick, value, panel.chart);
- this.penDown = true;
- return false;
- }
- if (this.accidentalClick(tick, value)) return this.dragToDraw;
-
- this.setPoint(1, tick, value, panel.chart);
- this.penDown = false;
- return true; // kernel will call render after this
-};
-
-/**
- * Default adjust function for BaseTwoPoint drawings
- * @memberOf CIQ.Drawing.BaseTwoPoint
- */
-CIQ.Drawing.BaseTwoPoint.prototype.adjust = function () {
- // If the drawing's panel doesn't exist then we'll check to see
- // whether the panel has been added. If not then there's no way to adjust
- var panel = this.stx.panels[this.panelName];
- if (!panel) return;
- this.setPoint(0, this.d0, this.v0, panel.chart);
- this.setPoint(1, this.d1, this.v1, panel.chart);
-};
-
-/**
- * Default move function for BaseTwoPoint drawings
- * @param {CanvasRenderingContext2D} context Canvas context on which to render.
- * @param {number} tick Tick in the `dataSet`.
- * @param {number} value Value at position.
- * @memberOf CIQ.Drawing.BaseTwoPoint
- */
-CIQ.Drawing.BaseTwoPoint.prototype.move = function (context, tick, value) {
- if (!this.penDown) return;
-
- this.copyConfig();
- this.p1 = [tick, value];
- this.render(context);
-};
-
-/**
- * Default measure function for BaseTwoPoint drawings
- * @memberOf CIQ.Drawing.BaseTwoPoint
- */
-CIQ.Drawing.BaseTwoPoint.prototype.measure = function () {
- if (this.p0 && this.p1) {
- this.stx.setMeasure(
- this.p0[1],
- this.p1[1],
- this.p0[0],
- this.p1[0],
- true,
- this.name
- );
- var mSticky = this.stx.controls.mSticky;
- var mStickyInterior = mSticky && mSticky.querySelector(".mStickyInterior");
- if (mStickyInterior) {
- var lines = [];
- lines.push(CIQ.capitalize(this.name));
- if (this.getYValue)
- lines.push(this.field || this.stx.defaultPlotField || "Close");
- lines.push(mStickyInterior.innerHTML);
- mStickyInterior.innerHTML = lines.join("
");
- }
- }
-};
-
-CIQ.Drawing.BaseTwoPoint.prototype.reposition = function (
- context,
- repositioner,
- tick,
- value
-) {
- if (!repositioner) return;
- var panel = this.stx.panels[this.panelName];
- var tickDiff = repositioner.tick - tick;
- var valueDiff = repositioner.value - value;
- if (repositioner.action == "move") {
- this.setPoint(
- 0,
- repositioner.p0[0] - tickDiff,
- repositioner.p0[1] - valueDiff,
- panel.chart
- );
- this.setPoint(
- 1,
- repositioner.p1[0] - tickDiff,
- repositioner.p1[1] - valueDiff,
- panel.chart
- );
- this.render(context);
- } else if (repositioner.action == "drag") {
- this[repositioner.point] = [tick, value];
- this.setPoint(0, this.p0[0], this.p0[1], panel.chart);
- this.setPoint(1, this.p1[0], this.p1[1], panel.chart);
- this.render(context);
- }
-};
-
-CIQ.Drawing.BaseTwoPoint.prototype.drawDropZone = function (
- context,
- hBound1,
- hBound2,
- leftBound,
- rightBound
-) {
- var panel = this.stx.panels[this.panelName];
- if (!panel) return;
- var x0 = panel.left;
- var x1 = panel.width;
- if (leftBound || leftBound === 0)
- x0 = this.stx.pixelFromTick(leftBound, panel.chart);
- if (rightBound || rightBound === 0)
- x1 = this.stx.pixelFromTick(rightBound, panel.chart);
- var y0 = this.stx.pixelFromPrice(hBound1, panel);
- var y1 = this.stx.pixelFromPrice(hBound2, panel);
- context.fillStyle = "#008000";
- context.globalAlpha = 0.2;
- context.fillRect(x0, y0, x1 - x0, y1 - y0);
- context.globalAlpha = 1;
-};
-
-/**
- * Annotation drawing tool. An annotation is a simple text tool. It uses the class stx_annotation
- * to determine the font style and color for the annotation. Class stx_annotation_highlight_bg is used to
- * determine the background color when highlighted.
- *
- * The controls controls.annotationSave and controls.annotationCancel are used to create HTMLElements for
- * saving and canceling the annotation while editing. A textarea is created dynamically. The annotation tool
- * attempts to draw the annotations at the same size and position as the textarea so that the effect is wysiwig.
- * @constructor
- * @name CIQ.Drawing.annotation
- * @see {@link CIQ.Drawing.BaseTwoPoint}
- */
-CIQ.Drawing.annotation = function () {
- this.name = "annotation";
- this.arr = [];
- this.w = 0;
- this.h = 0;
- this.padding = 4;
- this.text = "";
- this.ta = null;
- this.fontSize = 0;
- this.font = {};
-};
-CIQ.inheritsFrom(CIQ.Drawing.annotation, CIQ.Drawing.BaseTwoPoint);
-
-CIQ.Drawing.annotation.prototype.getFontString = function () {
- this.fontDef = {
- style: null,
- weight: null,
- size: "12px",
- family: null
- };
- var css = this.stx.canvasStyle("stx_annotation");
- if (css) {
- if (css.fontStyle) this.fontDef.style = css.fontStyle;
- if (css.fontWeight) this.fontDef.weight = css.fontWeight;
- if (css.fontSize) this.fontDef.size = css.fontSize;
- if (css.fontFamily) this.fontDef.family = css.fontFamily;
- }
- if (this.font.style) this.fontDef.style = this.font.style;
- if (this.font.weight) this.fontDef.weight = this.font.weight;
- if (this.font.size) this.fontDef.size = this.font.size;
- if (this.font.family) this.fontDef.family = this.font.family;
- this.fontString = "";
- var first = true;
- for (var n in this.fontDef) {
- if (this.fontDef[n]) {
- if (!first) {
- this.fontString += " ";
- } else {
- first = false;
- }
- this.fontString += this.fontDef[n];
- }
- }
-};
-
-CIQ.Drawing.annotation.prototype.configs = ["color", "font"];
-
-CIQ.Drawing.annotation.prototype.measure = function () {};
-
-CIQ.Drawing.annotation.prototype.render = function (context) {
- if (this.ta) return;
- var panel = this.stx.panels[this.panelName];
- if (!panel) return;
- var x0 = this.stx.pixelFromTick(this.p0[0], panel.chart);
- var y0 = this.stx.pixelFromValueAdjusted(panel, this.p0[0], this.p0[1]);
-
- context.font = this.fontString;
- context.textBaseline = "middle";
- var x = x0;
- var y = y0;
- var w = this.w;
- var h = this.h;
-
- var color = this.getLineColor();
- if (this.stem) {
- var sx0, sx1, sy0, sy1;
- if (this.stem.d) {
- // absolute positioning of stem
- sx0 = this.stx.pixelFromTick(this.stem.t); // bottom of stem
- sy0 = this.stx.pixelFromValueAdjusted(panel, this.stem.t, this.stem.v);
- sx1 = x + w / 2; // center of text
- sy1 = y + h / 2;
- } else if (this.stem.x) {
- // stem with relative offset positioning
- sx0 = x;
- sy0 = y;
- x += this.stem.x;
- y += this.stem.y;
- sx1 = x + w / 2;
- sy1 = y + h / 2;
- }
-
- context.beginPath();
- if (this.borderColor) context.strokeStyle = this.borderColor;
- else context.strokeStyle = color;
- context.moveTo(sx0, sy0);
- context.lineTo(sx1, sy1);
- context.stroke();
- }
- var lineWidth = context.lineWidth;
- if (this.highlighted) {
- this.stx.canvasColor("stx_annotation_highlight_bg", context);
- context.fillRect(
- x - lineWidth,
- y - h / 2 - lineWidth,
- w + 2 * lineWidth,
- h + 2 * lineWidth
- );
- } else {
- if (this.fillColor) {
- context.fillStyle = this.fillColor;
- context.fillRect(x, y - h / 2, w, h);
- } else if (this.stem) {
- // If there's a stem then use the container color otherwise the stem will show through
- context.fillStyle = this.stx.containerColor;
- context.fillRect(x, y - h / 2, w, h);
- }
- }
- if (this.borderColor) {
- context.beginPath();
- context.strokeStyle = this.highlighted
- ? this.stx.getCanvasColor("stx_highlight_vector")
- : this.borderColor;
- context.rect(
- x - lineWidth,
- y - h / 2 - lineWidth,
- w + 2 * lineWidth,
- h + 2 * lineWidth
- );
- context.stroke();
- }
-
- if (this.highlighted) {
- this.stx.canvasColor("stx_annotation_highlight", context);
- } else {
- context.fillStyle = color;
- }
- y += this.padding / 2;
- if (!this.ta) {
- for (var i = 0; i < this.arr.length; i++) {
- context.fillText(
- this.arr[i],
- x + this.padding,
- y - h / 2 + this.fontSize / 2
- );
- y += this.fontSize + 2; // 2 px space between lines
- }
- }
- context.textBaseline = "alphabetic";
-};
-
-CIQ.Drawing.annotation.prototype.onChange = function (e) {
- //no operation. Override if you want to capture the change.
-};
-
-CIQ.Drawing.annotation.prototype.edit = function (context, editExisting) {
- var panel = this.stx.panels[this.panelName];
- if (!panel) return;
- // When mouse events are attached to the container then any dom objects on top
- // of the container will intercept those events. In particular, the textarea for
- // annotations gets in the way, so here we capture the mouseup that fires on the textarea
- // and pass it along to the kernel if necessary
- function handleTAMouseUp(stx) {
- return function (e) {
- if (stx.manageTouchAndMouse && CIQ.ChartEngine.drawingLine) {
- stx.mouseup(e);
- }
- };
- }
-
- function cancelAnnotation(self) {
- return function (e) {
- var stx = self.stx;
- stx.editingAnnotation = false;
- stx.undo();
- stx.cancelTouchSingleClick = true;
- };
- }
- function saveAnnotation(self) {
- return function (e) {
- if (self.ta.value === "") return;
- self.text = self.ta.value;
- var stx = self.stx;
- stx.editingAnnotation = false;
- self.adjust();
- if (stx.drawingSnapshot)
- stx.undoStamp(
- CIQ.shallowClone(stx.drawingSnapshot),
- stx.exportDrawings()
- );
- else stx.addDrawing(self); // add only if it's not already there (text being modified)
- stx.undo();
- stx.cancelTouchSingleClick = true;
- stx.changeOccurred("vector");
- };
- }
-
- function resizeAnnotation(self) {
- return function (e) {
- if (e) {
- var key = e.keyCode;
- switch (key) {
- case 27:
- self.stx.undo();
- return;
- }
- }
- var stx = self.stx;
- var ta = self.ta;
- var arr = ta.value.split("\n");
- var w = 0;
- //stx.canvasFont("stx_annotation");
- stx.chart.context.font = self.fontString;
- for (var i = 0; i < arr.length; i++) {
- var m = stx.chart.context.measureText(arr[i]).width;
- if (m > w) w = m;
- }
- var h = (arr.length + 1) * (self.fontSize + 3);
- if (w < 50) w = 50;
- ta.style.width = w + 30 + "px"; // Leave room for scroll bar
- ta.style.height = h + "px";
- var y = parseInt(CIQ.stripPX(ta.style.top), 10);
- var x = CIQ.stripPX(ta.style.left);
- w = ta.clientWidth;
- h = ta.clientHeight;
- if (x + w + 100 < self.stx.chart.canvasWidth) {
- save.style.top = y + "px";
- cancel.style.top = y + "px";
- save.style.left = x + w + 10 + "px";
- cancel.style.left = x + w + 60 + "px";
- } else if (y + h + 30 < self.stx.chart.canvasHeight) {
- save.style.top = y + h + 10 + "px";
- cancel.style.top = y + h + 10 + "px";
- save.style.left = x + "px";
- cancel.style.left = x + 50 + "px";
- } else {
- save.style.top = y - 35 + "px";
- cancel.style.top = y - 35 + "px";
- save.style.left = x + "px";
- cancel.style.left = x + 50 + "px";
- }
- };
- }
-
- var save = this.stx.controls.annotationSave;
- var cancel = this.stx.controls.annotationCancel;
- if (!save || !cancel) return;
-
- var stx = this.stx,
- ta = this.ta;
- stx.editingAnnotation = true;
- stx.undisplayCrosshairs();
- stx.openDialog = "annotation";
- if (!ta) {
- ta = this.ta = document.createElement("TEXTAREA");
- ta.className = "stx_annotation";
- ta.onkeyup = resizeAnnotation(this);
- ta.onmouseup = handleTAMouseUp(stx);
- ta.setAttribute("wrap", "hard");
- if (CIQ.isIOS7or8) ta.setAttribute("placeholder", "Enter Text");
- stx.chart.container.appendChild(ta);
- ta.style.position = "absolute";
- ta.style.width = "100px";
- ta.style.height = "20px";
- ta.value = this.text;
- if (CIQ.touchDevice) {
- ta.ontouchstart = function (e) {
- e.stopPropagation();
- };
- /*var ta=this.ta;
- CIQ.safeClickTouch(this.ta, function(e){
- if(document.activeElement===ta){
- window.focus();
- CIQ.focus(ta, true);
- }
- });*/
- }
- }
- var self = this;
- ta.oninput = function (e) {
- // disable browser undo history due to hidden textarea with contenteditable
- if (e.inputType != "historyUndo" && e.inputType != "historyRedo")
- self.onChange(e);
- };
- ta.style.font = this.fontString;
- if (this.color) {
- if (this.color == "transparent" || this.color == "auto") {
- var styles = getComputedStyle(ta);
- if (styles && CIQ.isTransparent(styles.backgroundColor)) {
- ta.style.color = stx.defaultColor;
- } else {
- ta.style.color = "#000"; // text area always has white background
- }
- } else {
- ta.style.color = this.color;
- }
- }
- var x0 = stx.pixelFromTick(this.p0[0], panel.chart);
- var y0 = stx.pixelFromValueAdjusted(panel, this.p0[0], this.p0[1]);
- //if the right edge of the ta is off of the screen, scootch it to the left.
- ta.style.left =
- x0 + 140 < stx.chart.canvasRight
- ? x0 + "px"
- : stx.chart.canvasRight - 200 + "px";
- //if user clicks within 60 px of bottom of the chart,scootch it up.
- ta.style.top =
- y0 + 60 < stx.chart.canvasHeight
- ? y0 - (!isNaN(this.h) ? this.h / 2 : this.defaultHeight) + "px"
- : y0 - 60 + "px";
- if (this.name == "callout") {
- ta.style.left =
- CIQ.stripPX(ta.style.left) -
- (!isNaN(this.w) ? this.w / 2 : this.defaultWidth) +
- "px";
- }
-
- CIQ.safeClickTouch(save, saveAnnotation(this));
- CIQ.safeClickTouch(cancel, cancelAnnotation(this));
- resizeAnnotation(this)();
- save.style.display = "inline-block";
- cancel.style.display = "inline-block";
-
- if (editExisting) {
- // lift the drawing off the canvas and onto the tempCanvas
- stx.drawingSnapshot = stx.exportDrawings();
- this.hidden = true;
- stx.draw();
- stx.activeDrawing = this;
- CIQ.ChartEngine.drawingLine = true;
- context = stx.chart.tempCanvas.context;
- stx.chart.tempCanvas.style.display = "block";
- this.w = ta.clientWidth;
- this.h = ta.clientHeight;
- CIQ.clearCanvas(context.canvas, stx);
- this.render(context);
- this.edit(context);
- }
-
- ta.focus();
-
- if (CIQ.isAndroid && !CIQ.is_chrome && !CIQ.isFF) {
- // Android soft keyboard will cover up the lower half of the browser so if our
- // annotation is in that area we temporarily scroll the chart container upwards
- // The style.bottom of the chart container is reset in abort()
- this.priorBottom = stx.chart.container.style.bottom;
- var keyboardHeight = 400; // hard coded. We could get this by measuring the change in innerHeight but timing is awkward because the keyboard scrolls
- var screenLocation = stx.resolveY(y0) + 100; // figure 100 pixels of height for text
- if (screenLocation > CIQ.pageHeight() - keyboardHeight) {
- var pixelsFromBottomOfScreen = CIQ.pageHeight() - screenLocation;
- var scrolledBottom = keyboardHeight - pixelsFromBottomOfScreen;
- stx.chart.container.style.bottom = scrolledBottom + "px";
- }
- }
-};
-
-CIQ.Drawing.annotation.prototype.click = function (context, tick, value) {
- //don't allow user to add annotation on the axis.
- if (this.stx.overXAxis || this.stx.overYAxis) return;
- var panel = this.stx.panels[this.panelName];
- this.copyConfig();
- //this.getFontString();
- this.setPoint(0, tick, value, panel.chart);
- this.adjust();
-
- this.edit(context);
- return false;
-};
-
-CIQ.Drawing.annotation.prototype.reposition = function (
- context,
- repositioner,
- tick,
- value
-) {
- if (!repositioner) return;
- var panel = this.stx.panels[this.panelName];
- var tickDiff = repositioner.tick - tick;
- var valueDiff = repositioner.value - value;
- this.setPoint(
- 0,
- repositioner.p0[0] - tickDiff,
- repositioner.p0[1] - valueDiff,
- panel.chart
- );
- this.render(context);
-};
-
-CIQ.Drawing.annotation.prototype.intersected = function (tick, value, box) {
- var panel = this.stx.panels[this.panelName];
- if (!this.p0) return null; // in case invalid drawing (such as from panel that no longer exists)
- var x0 = this.stx.pixelFromTick(this.p0[0], panel.chart);
- var y0 =
- this.stx.pixelFromValueAdjusted(panel, this.p0[0], this.p0[1]) - this.h / 2;
- var x1 = x0 + this.w;
- var y1 = y0 + this.h;
- if (this.stem && this.stem.x) {
- x0 += this.stem.x;
- x1 += this.stem.x;
- y0 += this.stem.y;
- y1 += this.stem.y;
- }
- var x = this.stx.pixelFromTick(tick, panel.chart);
- var y = this.stx.pixelFromValueAdjusted(panel, tick, value);
-
- if (
- x + box.r >= x0 &&
- x - box.r <= x1 &&
- y + box.r >= y0 &&
- y - box.r <= y1
- ) {
- this.highlighted = true;
- return {
- p0: CIQ.clone(this.p0),
- tick: tick,
- value: value
- };
- }
- return false;
-};
-
-CIQ.Drawing.annotation.prototype.abort = function () {
- var save = this.stx.controls.annotationSave,
- cancel = this.stx.controls.annotationCancel;
- if (save) save.style.display = "none";
- if (cancel) cancel.style.display = "none";
- if (this.ta) this.stx.chart.container.removeChild(this.ta);
- this.ta = null;
- this.stx.openDialog = "";
- this.stx.showCrosshairs();
- //document.body.style.cursor="crosshair"; //Was interfering with undisplayCrosshairs().
- this.stx.editingAnnotation = false;
- CIQ.clearCanvas(this.stx.chart.tempCanvas, this.stx);
- if (CIQ.isAndroid && !CIQ.is_chrome && !CIQ.isFF) {
- this.stx.chart.container.style.bottom = this.priorBottom;
- }
- CIQ.fixScreen();
-};
-
-/**
- * Reconstruct an annotation
- * @param {CIQ.ChartEngine} stx The chart object
- * @param {object}[obj] A drawing descriptor
- * @param {string} [obj.col] The text color for the annotation
- * @param {string} [obj.pnl] The panel name
- * @param {string} [obj.d0] String form date or date time
- * @param {number} [obj.v0] The value at which to position the annotation
- * @param {string} [obj.text] The annotation text (escaped using encodeURIComponent())
- * @param {number} [obj.tzo0] Offset of UTC from d0 in minutes
- * @param {string} [obj.bc] Border color
- * @param {string} [obj.bg] Background color
- * @param {string} [obj.lw] Line width
- * @param {string} [obj.ptrn] Line pattern
- * @param {object} [obj.fnt] Font
- * @param {object} [obj.fnt.st] Font style
- * @param {object} [obj.fnt.sz] Font size
- * @param {object} [obj.fnt.wt] Font weight
- * @param {object} [obj.fnt.fl] Font family
- * @memberOf CIQ.Drawing.annotation
- */
-CIQ.Drawing.annotation.prototype.reconstruct = function (stx, obj) {
- this.stx = stx;
- this.color = obj.col;
- this.panelName = obj.pnl;
- this.d0 = obj.d0;
- this.tzo0 = obj.tzo0;
- this.v0 = obj.v0;
- this.text = stx.escapeOnSerialize ? decodeURIComponent(obj.text) : obj.text;
- this.stem = obj.stem;
- this.borderColor = obj.bc;
- this.fillColor = obj.bg;
- this.lineWidth = obj.lw;
- this.pattern = obj.ptrn;
- this.font = CIQ.replaceFields(obj.fnt, {
- st: "style",
- sz: "size",
- wt: "weight",
- fl: "family"
- });
- if (!this.font) this.font = {};
- this.adjust();
-};
-
-CIQ.Drawing.annotation.prototype.serialize = function () {
- var obj = {
- name: this.name,
- pnl: this.panelName,
- col: this.color,
- d0: this.d0,
- tzo0: this.tzo0,
- v0: this.v0,
- text: this.stx.escapeOnSerialize ? encodeURIComponent(this.text) : this.text
- };
- if (this.font) {
- var fnt = CIQ.removeNullValues(
- CIQ.replaceFields(this.font, {
- style: "st",
- size: "sz",
- weight: "wt",
- family: "fl"
- })
- );
- if (!CIQ.isEmpty(fnt)) obj.fnt = fnt;
- }
- if (this.stem) {
- obj.stem = {
- d: this.stem.d,
- v: this.stem.v,
- x: this.stem.x,
- y: this.stem.y
- };
- }
- if (this.borderColor) obj.bc = this.borderColor;
- if (this.fillColor) obj.bg = this.fillColor;
- if (this.lineWidth) obj.lw = this.lineWidth;
- if (this.pattern) obj.ptrn = this.pattern;
-
- return obj;
-};
-
-CIQ.Drawing.annotation.prototype.renderText = function () {
- this.getFontString();
- var panel = this.stx.panels[this.panelName];
- if (!panel) return;
- this.arr = this.text.split("\n");
- var w = 0;
- this.stx.chart.context.font = this.fontString;
- //this.stx.canvasFont("stx_annotation");
- for (var i = 0; i < this.arr.length; i++) {
- var m = this.stx.chart.context.measureText(this.arr[i]).width;
- if (m > w) w = m;
- }
- if (w === 0) w = 2 * this.defaultWidth;
- //this.fontSize=this.stx.getCanvasFontSize("stx_annotation");
- this.fontSize = CIQ.stripPX(this.fontDef.size);
- var h = this.arr.length * (this.fontSize + 2); // 2 px space to separate lines
- if (CIQ.touchDevice) h += 5;
- this.w = w + this.padding * 2;
- this.h = h + this.padding * 2;
- var x1 = this.stx.pixelFromTick(this.p0[0], panel.chart) + w;
- var y1 = this.stx.pixelFromValueAdjusted(panel, this.p0[0], this.p0[1]) + h;
- this.p1 = [
- this.stx.tickFromPixel(x1, panel.chart),
- this.stx.valueFromPixel(y1, panel)
- ];
- if (this.stem && this.stem.d) {
- this.stem.t = this.stx.tickFromDate(this.stem.d, panel.chart);
- }
-};
-
-CIQ.Drawing.annotation.prototype.adjust = function () {
- var panel = this.stx.panels[this.panelName];
- if (!panel) return;
- this.setPoint(0, this.d0, this.v0, panel.chart);
- this.renderText();
-};
-
-/**
- * segment is an implementation of a {@link CIQ.Drawing.BaseTwoPoint} drawing.
- * @name CIQ.Drawing.segment
- * @constructor
- */
-CIQ.Drawing.segment = function () {
- this.name = "segment";
-};
-
-CIQ.inheritsFrom(CIQ.Drawing.segment, CIQ.Drawing.BaseTwoPoint);
-
-CIQ.Drawing.segment.prototype.render = function (context) {
- var panel = this.stx.panels[this.panelName];
- if (!panel) return;
- var x0 = this.stx.pixelFromTick(this.p0[0], panel.chart);
- var x1 = this.stx.pixelFromTick(this.p1[0], panel.chart);
- var y0 = this.stx.pixelFromValueAdjusted(panel, this.p0[0], this.p0[1]);
- var y1 = this.stx.pixelFromValueAdjusted(panel, this.p1[0], this.p1[1]);
-
- var width = this.lineWidth;
- var color = this.getLineColor();
-
- var parameters = {
- pattern: this.pattern,
- lineWidth: width
- };
- if (parameters.pattern == "none") parameters.pattern = "solid";
- this.stx.plotLine(
- x0,
- x1,
- y0,
- y1,
- color,
- this.name,
- context,
- panel,
- parameters
- );
-
- if (this.axisLabel && !this.repositioner) {
- if (this.name == "horizontal") {
- this.stx.endClip();
- var txt = this.p0[1];
- if (panel.chart.transformFunc)
- txt = panel.chart.transformFunc(this.stx, panel.chart, txt);
- if (panel.yAxis.priceFormatter)
- txt = panel.yAxis.priceFormatter(this.stx, panel, txt);
- else txt = this.stx.formatYAxisPrice(txt, panel);
- this.stx.createYAxisLabel(panel, txt, y0, color);
- this.stx.startClip(panel.name);
- } else if (
- this.name == "vertical" &&
- this.p0[0] >= 0 &&
- !this.stx.chart.xAxis.noDraw
- ) {
- // don't try to compute dates from before dataSet
- var dt, newDT;
- dt = this.stx.dateFromTick(this.p0[0], panel.chart, true);
- if (!CIQ.ChartEngine.isDailyInterval(this.stx.layout.interval)) {
- var milli = dt.getSeconds() * 1000 + dt.getMilliseconds();
- if (timezoneJS.Date && this.stx.displayZone) {
- // this converts from the quote feed timezone to the chart specified time zone
- newDT = new timezoneJS.Date(dt.getTime(), this.stx.displayZone);
- dt = new Date(
- newDT.getFullYear(),
- newDT.getMonth(),
- newDT.getDate(),
- newDT.getHours(),
- newDT.getMinutes()
- );
- dt = new Date(dt.getTime() + milli);
- }
- } else {
- dt.setHours(0, 0, 0, 0);
- }
- var myDate = CIQ.mmddhhmm(CIQ.yyyymmddhhmm(dt));
-
- if (panel.chart.xAxis.formatter) {
- myDate = panel.chart.xAxis.formatter(dt, this.name, null, null, myDate);
- } else if (this.stx.internationalizer) {
- var str;
- if (dt.getHours() !== 0 || dt.getMinutes() !== 0) {
- str = this.stx.internationalizer.monthDay.format(dt);
- str += " " + this.stx.internationalizer.hourMinute.format(dt);
- } else {
- str = this.stx.internationalizer.yearMonthDay.format(dt);
- }
- myDate = str;
- }
-
- this.stx.endClip();
- this.stx.createXAxisLabel({
- panel: panel,
- txt: myDate,
- x: x0,
- backgroundColor: color,
- color: null,
- pointed: true,
- padding: 2
- });
- this.stx.startClip(panel.name);
- }
- }
- if (
- this.highlighted &&
- this.name != "horizontal" &&
- this.name != "vertical"
- ) {
- var p0Fill = this.highlighted == "p0" ? true : false;
- var p1Fill = this.highlighted == "p1" ? true : false;
- this.littleCircle(context, x0, y0, p0Fill);
- this.littleCircle(context, x1, y1, p1Fill);
- }
-};
-
-CIQ.Drawing.segment.prototype.abort = function () {
- this.stx.setMeasure(null, null, null, null, false);
-};
-
-CIQ.Drawing.segment.prototype.intersected = function (tick, value, box) {
- if (!this.p0 || !this.p1) return null; // in case invalid drawing (such as from panel that no longer exists)
- var name = this.name;
- if (name != "horizontal" && name != "vertical" && name != "gartley") {
- var pointsToCheck = { 0: this.p0, 1: this.p1 };
- for (var pt in pointsToCheck) {
- if (
- this.pointIntersection(pointsToCheck[pt][0], pointsToCheck[pt][1], box)
- ) {
- this.highlighted = "p" + pt;
- return {
- action: "drag",
- point: "p" + pt
- };
- }
- }
- }
- if (name == "horizontal" || name == "vertical") name = "line";
- var isIntersected = this.lineIntersection(tick, value, box, name);
- if (isIntersected) {
- this.highlighted = true;
- // This object will be used for repositioning
- return {
- action: "move",
- p0: CIQ.clone(this.p0),
- p1: CIQ.clone(this.p1),
- tick: tick, // save original tick
- value: value // save original value
- };
- }
- return null;
-};
-
-CIQ.Drawing.segment.prototype.configs = ["color", "lineWidth", "pattern"];
-
-CIQ.Drawing.segment.prototype.copyConfig = function (withPreferences) {
- CIQ.Drawing.copyConfig(this, withPreferences);
- if (this.pattern == "none" && this.configs.indexOf("fillColor") == -1)
- this.pattern = "solid";
-};
-
-/**
- * Reconstruct a segment
- * @memberOf CIQ.Drawing.segment
- * @param {CIQ.ChartEngine} stx The chart object
- * @param {object} [obj] A drawing descriptor
- * @param {string} [obj.col] The line color
- * @param {string} [obj.pnl] The panel name
- * @param {string} [obj.ptrn] Optional pattern for line "solid","dotted","dashed". Defaults to solid.
- * @param {number} [obj.lw] Optional line width. Defaults to 1.
- * @param {number} [obj.v0] Value (price) for the first point
- * @param {number} [obj.v1] Value (price) for the second point
- * @param {number} [obj.d0] Date (string form) for the first point
- * @param {number} [obj.d1] Date (string form) for the second point
- * @param {number} [obj.tzo0] Offset of UTC from d0 in minutes
- * @param {number} [obj.tzo1] Offset of UTC from d1 in minutes
- */
-CIQ.Drawing.segment.prototype.reconstruct = function (stx, obj) {
- this.stx = stx;
- this.color = obj.col;
- this.panelName = obj.pnl;
- this.pattern = obj.ptrn;
- this.lineWidth = obj.lw;
- this.d0 = obj.d0;
- this.d1 = obj.d1;
- this.tzo0 = obj.tzo0;
- this.tzo1 = obj.tzo1;
- this.v0 = obj.v0;
- this.v1 = obj.v1;
- this.adjust();
-};
-
-CIQ.Drawing.segment.prototype.serialize = function () {
- return {
- name: this.name,
- pnl: this.panelName,
- col: this.color,
- ptrn: this.pattern,
- lw: this.lineWidth,
- d0: this.d0,
- d1: this.d1,
- tzo0: this.tzo0,
- tzo1: this.tzo1,
- v0: this.v0,
- v1: this.v1
- };
-};
-
-/**
- * Line drawing tool. A line is a vector defined by two points that is infinite in both directions.
- *
- * It inherits its properties from {@link CIQ.Drawing.segment}.
- * @constructor
- * @name CIQ.Drawing.line
- */
-CIQ.Drawing.line = function () {
- this.name = "line";
-};
-
-CIQ.inheritsFrom(CIQ.Drawing.line, CIQ.Drawing.segment);
-
-CIQ.Drawing.line.prototype.dragToDraw = false;
-
-CIQ.Drawing.line.prototype.calculateOuterSet = function (panel) {
- if (
- this.p0[0] == this.p1[0] ||
- this.p0[1] == this.p1[1] ||
- CIQ.ChartEngine.isDailyInterval(this.stx.layout.interval)
- ) {
- return;
- }
-
- var vector = {
- x0: this.p0[0],
- y0: this.p0[1],
- x1: this.p1[0],
- y1: this.p1[1]
- };
- if (vector.x0 > vector.x1) {
- vector = {
- x0: this.p1[0],
- y0: this.p1[1],
- x1: this.p0[0],
- y1: this.p0[1]
- };
- }
-
- var earlier = vector.x0 - 1000;
- var later = vector.x1 + 1000;
-
- this.v0B = CIQ.yIntersection(vector, earlier);
- this.v1B = CIQ.yIntersection(vector, later);
- this.d0B = this.stx.dateFromTick(earlier, panel.chart);
- this.d1B = this.stx.dateFromTick(later, panel.chart);
-};
-
-CIQ.Drawing.line.prototype.click = function (context, tick, value) {
- var panel = this.stx.panels[this.panelName];
- if (!panel) return;
- this.copyConfig();
- if (!this.penDown) {
- this.setPoint(0, tick, value, panel.chart);
- this.penDown = true;
- return false;
- }
- // if the user accidentally double clicks in rapid fashion
- if (this.accidentalClick(tick, value)) return this.dragToDraw;
- this.setPoint(1, tick, value, panel.chart);
- this.calculateOuterSet(panel);
- this.penDown = false;
- return true; // kernel will call render after this
-};
-
-/**
- * Reconstruct a line
- * @param {CIQ.ChartEngine} stx The chart object
- * @param {object} [obj] A drawing descriptor
- * @param {string} [obj.col] The line color
- * @param {string} [obj.pnl] The panel name
- * @param {string} [obj.ptrn] Optional pattern for line "solid","dotted","dashed". Defaults to solid.
- * @param {number} [obj.lw] Optional line width. Defaults to 1.
- * @param {number} [obj.v0] Value (price) for the first point
- * @param {number} [obj.v1] Value (price) for the second point
- * @param {number} [obj.d0] Date (string form) for the first point
- * @param {number} [obj.d1] Date (string form) for the second point
- * @param {number} [obj.v0B] Computed outer Value (price) for the first point if original drawing was on intraday but now displaying on daily
- * @param {number} [obj.v1B] Computed outer Value (price) for the second point if original drawing was on intraday but now displaying on daily
- * @param {number} [obj.d0B] Computed outer Date (string form) for the first point if original drawing was on intraday but now displaying on daily
- * @param {number} [obj.d1B] Computed outer Date (string form) for the second point if original drawing was on intraday but now displaying on daily
- * @param {number} [obj.tzo0] Offset of UTC from d0 in minutes
- * @param {number} [obj.tzo1] Offset of UTC from d1 in minutes
- * @memberOf CIQ.Drawing.line
- */
-CIQ.Drawing.line.prototype.reconstruct = function (stx, obj) {
- this.stx = stx;
- this.color = obj.col;
- this.panelName = obj.pnl;
- this.pattern = obj.ptrn;
- this.lineWidth = obj.lw;
- this.v0 = obj.v0;
- this.v1 = obj.v1;
- this.d0 = obj.d0;
- this.d1 = obj.d1;
- this.tzo0 = obj.tzo0;
- this.tzo1 = obj.tzo1;
- if (obj.d0B) {
- this.d0B = obj.d0B;
- this.d1B = obj.d1B;
- this.v0B = obj.v0B;
- this.v1B = obj.v1B;
- }
- this.adjust();
-};
-
-CIQ.Drawing.line.prototype.serialize = function () {
- var obj = {
- name: this.name,
- pnl: this.panelName,
- col: this.color,
- ptrn: this.pattern,
- lw: this.lineWidth,
- d0: this.d0,
- d1: this.d1,
- tzo0: this.tzo0,
- tzo1: this.tzo1,
- v0: this.v0,
- v1: this.v1
- };
- if (this.d0B) {
- obj.d0B = this.d0B;
- obj.d1B = this.d1B;
- obj.v0B = this.v0B;
- obj.v1B = this.v1B;
- }
- return obj;
-};
-
-CIQ.Drawing.line.prototype.adjust = function () {
- var panel = this.stx.panels[this.panelName];
- if (!panel) return;
- this.setPoint(0, this.d0, this.v0, panel.chart);
- this.setPoint(1, this.d1, this.v1, panel.chart);
- // Use outer set if original drawing was on intraday but now displaying on daily
- if (CIQ.ChartEngine.isDailyInterval(this.stx.layout.interval) && this.d0B) {
- this.setPoint(0, this.d0B, this.v0B, panel.chart);
- this.setPoint(1, this.d1B, this.v1B, panel.chart);
- }
-};
-
-/**
- * Horizontal line drawing tool. The horizontal line extends infinitely in both directions.
- *
- * It inherits its properties from {@link CIQ.Drawing.segment}
- * @constructor
- * @name CIQ.Drawing.horizontal
- */
-CIQ.Drawing.horizontal = function () {
- this.name = "horizontal";
-};
-CIQ.inheritsFrom(CIQ.Drawing.horizontal, CIQ.Drawing.segment);
-
-CIQ.Drawing.horizontal.prototype.dragToDraw = false;
-
-CIQ.Drawing.horizontal.prototype.measure = function () {};
-
-CIQ.Drawing.horizontal.prototype.click = function (context, tick, value) {
- var panel = this.stx.panels[this.panelName];
- if (!panel) return;
- this.copyConfig();
- this.setPoint(0, tick, value, panel.chart);
- return true; // kernel will call render after this
-};
-
-// skips point interection and forces positioner points inside of the dataSet
-CIQ.Drawing.horizontal.prototype.intersected = function (tick, value, box) {
- if (this.lineIntersection(tick, value, box, "line")) {
- var stx = this.stx;
- var t0 = stx.chart.dataSet.length;
- var v0 = this.p0[1];
-
- this.highlighted = true;
-
- return {
- action: "move",
- p0: [t0 - 2, v0],
- p1: [t0 - 1, v0],
- tick: tick,
- value: value
- };
- }
-
- return null;
-};
-
-/**
- * Reconstruct a horizontal
- * @param {CIQ.ChartEngine} stx The chart object
- * @param {object} [obj] A drawing descriptor
- * @param {string} [obj.col] The line color
- * @param {string} [obj.pnl] The panel name
- * @param {string} [obj.ptrn] Optional pattern for line "solid","dotted","dashed". Defaults to solid.
- * @param {number} [obj.lw] Optional line width. Defaults to 1.
- * @param {number} [obj.v0] Value (price) for the first point
- * @param {number} [obj.d0] Date (string form) for the first point
- * @param {boolean} [obj.al] True to include an axis label
- * @param {number} [obj.tzo0] Offset of UTC from d0 in minutes
- * @memberOf CIQ.Drawing.horizontal
- */
-CIQ.Drawing.horizontal.prototype.reconstruct = function (stx, obj) {
- this.stx = stx;
- this.color = obj.col;
- this.panelName = obj.pnl;
- this.pattern = obj.ptrn;
- this.lineWidth = obj.lw;
- this.v0 = obj.v0;
- this.d0 = obj.d0;
- this.tzo0 = obj.tzo0;
- this.axisLabel = obj.al;
- this.adjust();
-};
-
-CIQ.Drawing.horizontal.prototype.serialize = function () {
- var obj = {
- name: this.name,
- pnl: this.panelName,
- col: this.color,
- ptrn: this.pattern,
- lw: this.lineWidth,
- v0: this.v0,
- d0: this.d0,
- tzo0: this.tzo0,
- al: this.axisLabel
- };
-
- return obj;
-};
-
-CIQ.Drawing.horizontal.prototype.adjust = function () {
- var panel = this.stx.panels[this.panelName];
- if (!panel) return;
- this.setPoint(0, this.d0, this.v0, panel.chart);
- this.p1 = [this.p0[0] + 100, this.p0[1]];
-};
-
-CIQ.Drawing.horizontal.prototype.configs = [
- "color",
- "lineWidth",
- "pattern",
- "axisLabel"
-];
-
-/**
- * Vertical line drawing tool. The vertical line extends infinitely in both directions.
- *
- * It inherits its properties from {@link CIQ.Drawing.horizontal}.
- * @constructor
- * @name CIQ.Drawing.vertical
- */
-CIQ.Drawing.vertical = function () {
- this.name = "vertical";
-};
-
-CIQ.inheritsFrom(CIQ.Drawing.vertical, CIQ.Drawing.horizontal);
-CIQ.Drawing.vertical.prototype.measure = function () {};
-
-// override specialized horizontal method
-CIQ.Drawing.vertical.prototype.intersected =
- CIQ.Drawing.segment.prototype.intersected;
-
-CIQ.Drawing.vertical.prototype.adjust = function () {
- var panel = this.stx.panels[this.panelName];
- if (!panel) return;
- this.setPoint(0, this.d0, this.v0, panel.chart);
- this.p1 = [this.p0[0], this.p0[1] + 1];
-};
-
-/**
- * Measure tool.
- * It inherits its properties from {@link CIQ.Drawing.segment}.
- * @constructor
- * @name CIQ.Drawing.measure
- */
-CIQ.Drawing.measure = function () {
- this.name = "measure";
-};
-
-CIQ.inheritsFrom(CIQ.Drawing.measure, CIQ.Drawing.segment);
-
-CIQ.Drawing.measure.prototype.click = function (context, tick, value) {
- this.copyConfig();
- if (!this.penDown) {
- this.p0 = [tick, value];
- this.penDown = true;
-
- return false;
- }
- this.stx.undo();
- this.penDown = false;
- return true;
-};
-
-/**
- * rectangle is an implementation of a {@link CIQ.Drawing.BaseTwoPoint} drawing
- * @constructor
- * @name CIQ.Drawing.rectangle
- */
-CIQ.Drawing.rectangle = function () {
- this.name = "rectangle";
-};
-
-CIQ.inheritsFrom(CIQ.Drawing.rectangle, CIQ.Drawing.BaseTwoPoint);
-
-CIQ.Drawing.rectangle.prototype.render = function (context) {
- var panel = this.stx.panels[this.panelName];
- if (!panel) return;
- var x0 = this.stx.pixelFromTick(this.p0[0], panel.chart);
- var x1 = this.stx.pixelFromTick(this.p1[0], panel.chart);
- var y0 = this.stx.pixelFromValueAdjusted(panel, this.p0[0], this.p0[1]);
- var y1 = this.stx.pixelFromValueAdjusted(panel, this.p1[0], this.p1[1]);
-
- var x = Math.round(Math.min(x0, x1)) + 0.5;
- var y = Math.min(y0, y1);
- var width = Math.max(x0, x1) - x;
- var height = Math.max(y0, y1) - y;
- var edgeColor = this.color;
- if (this.highlighted) {
- edgeColor = this.stx.getCanvasColor("stx_highlight_vector");
- }
-
- var fillColor = this.fillColor;
- if (fillColor && !CIQ.isTransparent(fillColor) && fillColor != "auto") {
- context.beginPath();
- context.rect(x, y, width, height);
- context.fillStyle = fillColor;
- context.globalAlpha = 0.2;
- context.fill();
- context.closePath();
- context.globalAlpha = 1;
- }
-
- var parameters = {
- pattern: this.pattern,
- lineWidth: this.lineWidth
- };
- if (this.highlighted && parameters.pattern == "none") {
- parameters.pattern = "solid";
- if (parameters.lineWidth == 0.1) parameters.lineWidth = 1;
- }
-
- // We extend the vertical lines by .5 to account for displacement of the horizontal lines
- // HTML5 Canvas exists *between* pixels, not on pixels, so draw on .5 to get crisp lines
- this.stx.plotLine(
- x0,
- x1,
- y0,
- y0,
- edgeColor,
- "segment",
- context,
- panel,
- parameters
- );
- this.stx.plotLine(
- x1,
- x1,
- y0 - 0.5,
- y1 + 0.5,
- edgeColor,
- "segment",
- context,
- panel,
- parameters
- );
- this.stx.plotLine(
- x1,
- x0,
- y1,
- y1,
- edgeColor,
- "segment",
- context,
- panel,
- parameters
- );
- this.stx.plotLine(
- x0,
- x0,
- y1 + 0.5,
- y0 - 0.5,
- edgeColor,
- "segment",
- context,
- panel,
- parameters
- );
- if (this.highlighted) {
- var p0Fill = this.highlighted == "p0" ? true : false;
- var p1Fill = this.highlighted == "p1" ? true : false;
- this.littleCircle(context, x0, y0, p0Fill);
- this.littleCircle(context, x1, y1, p1Fill);
- }
-};
-
-CIQ.Drawing.rectangle.prototype.intersected = function (tick, value, box) {
- if (!this.p0 || !this.p1) return null; // in case invalid drawing (such as from panel that no longer exists)
- var pointsToCheck = { 0: this.p0, 1: this.p1 };
- for (var pt in pointsToCheck) {
- if (
- this.pointIntersection(pointsToCheck[pt][0], pointsToCheck[pt][1], box)
- ) {
- this.highlighted = "p" + pt;
- return {
- action: "drag",
- point: "p" + pt
- };
- }
- }
- if (this.boxIntersection(tick, value, box)) {
- this.highlighted = true;
- return {
- action: "move",
- p0: CIQ.clone(this.p0),
- p1: CIQ.clone(this.p1),
- tick: tick,
- value: value
- };
- }
- return null;
-};
-
-CIQ.Drawing.rectangle.prototype.configs = [
- "color",
- "fillColor",
- "lineWidth",
- "pattern"
-];
-
-/**
- * Reconstruct an rectangle
- * @param {CIQ.ChartEngine} stx The chart object
- * @param {object} [obj] A drawing descriptor
- * @param {string} [obj.col] The border color
- * @param {string} [obj.fc] The fill color
- * @param {string} [obj.pnl] The panel name
- * @param {string} [obj.ptrn] Optional pattern for line "solid","dotted","dashed". Defaults to solid.
- * @param {number} [obj.lw] Optional line width. Defaults to 1.
- * @param {number} [obj.v0] Value (price) for the first point
- * @param {number} [obj.v1] Value (price) for the second point
- * @param {number} [obj.d0] Date (string form) for the first point
- * @param {number} [obj.d1] Date (string form) for the second point
- * @param {number} [obj.tzo0] Offset of UTC from d0 in minutes
- * @param {number} [obj.tzo1] Offset of UTC from d1 in minutes
- * @memberOf CIQ.Drawing.rectangle
- */
-CIQ.Drawing.rectangle.prototype.reconstruct = function (stx, obj) {
- this.stx = stx;
- this.color = obj.col;
- this.fillColor = obj.fc;
- this.panelName = obj.pnl;
- this.pattern = obj.ptrn;
- this.lineWidth = obj.lw;
- this.d0 = obj.d0;
- this.d1 = obj.d1;
- this.tzo0 = obj.tzo0;
- this.tzo1 = obj.tzo1;
- this.v0 = obj.v0;
- this.v1 = obj.v1;
- this.adjust();
-};
-
-CIQ.Drawing.rectangle.prototype.serialize = function () {
- return {
- name: this.name,
- pnl: this.panelName,
- col: this.color,
- fc: this.fillColor,
- ptrn: this.pattern,
- lw: this.lineWidth,
- d0: this.d0,
- d1: this.d1,
- tzo0: this.tzo0,
- tzo1: this.tzo1,
- v0: this.v0,
- v1: this.v1
- };
-};
-
-/**
- * shape is a default implementation of a {@link CIQ.Drawing.BaseTwoPoint} drawing
- * which places a "shape" on the canvas. It can be rotated and/or stretched.
- * It is meant to be overridden with specific shape designs, such as arrows....
- * @constructor
- * @name CIQ.Drawing.shape
- * @since 2015-11-1
- * @version ChartIQ Advanced Package
- */
-CIQ.Drawing.shape = function () {
- this.name = "shape";
- this.radians = 0;
- this.a = 0;
- this.rotating = false;
- this.textMeasure = false;
- this.configurator = "shape"; //forces all derived classes to default to shape drawing tools
- this.dimension = [0, 0];
- this.points = [];
-};
-
-CIQ.inheritsFrom(CIQ.Drawing.shape, CIQ.Drawing.BaseTwoPoint);
-
-/**
- * If true, enables rotation when the drawing is initially drawn.
- *
- * @type boolean
- * @default
- * @memberof CIQ.Drawing.shape
- * @since 7.4.0
- */
-CIQ.Drawing.shape.prototype.setRotationOnInitialDraw = false;
-
-CIQ.Drawing.shape.prototype.measure = function () {};
-
-CIQ.Drawing.shape.prototype.render = function (context) {
- if (!this.points.length) return;
- var panel = this.stx.panels[this.panelName];
- if (!panel) return;
- var x0 = this.stx.pixelFromTick(this.p0[0], panel.chart);
- var y0 = this.stx.pixelFromValueAdjusted(panel, this.p0[0], this.p0[1]);
- if (this.p1) {
- var x1 = this.stx.pixelFromTick(this.p1[0], panel.chart);
- var y1 = this.stx.pixelFromValueAdjusted(panel, this.p1[0], this.p1[1]);
-
- context.globalAlpha = 0.5;
- context.fillStyle = "#000000";
- if (this.rotating) {
- this.radians = Math.atan((y1 - y0) / (x1 - x0));
- if (x1 < x0) this.radians += Math.PI;
- else if (y1 < y0) this.radians += 2 * Math.PI;
- this.a = parseInt(((this.radians * 36) / Math.PI).toFixed(0), 10) * 5;
- this.a %= 360;
- this.radians = (this.a * Math.PI) / 180;
- if (this.textMeasure)
- context.fillText(this.a + "\u00b0", x1 + 10, y1 + 10);
- } else if (this.penDown) {
- this.sx = Math.max(
- 1,
- parseFloat(Math.abs((2 * (x1 - x0)) / this.dimension[0]).toFixed(1))
- );
- if (x1 < x0) this.sx *= -1;
- this.sy = Math.max(
- 1,
- parseFloat(Math.abs((2 * (y1 - y0)) / this.dimension[1]).toFixed(1))
- );
- if (y1 < y0) this.sy *= -1;
- if (this.textMeasure)
- context.fillText(
- this.sx + "x," + this.sy + "x",
- x1 + this.sx + 5,
- y1 + this.sy + 5
- );
- }
- context.globalAlpha = 1;
- }
- if (typeof this.sx === "undefined") {
- this.sx = this.sy = 1;
- }
-
- var lineWidth = this.lineWidth;
- if (!lineWidth) lineWidth = 1.1;
-
- var parameters = {
- pattern: this.pattern,
- lineWidth: lineWidth
- };
- if (this.highlighted && parameters.pattern == "none") {
- parameters.pattern = "solid";
- if (parameters.lineWidth == 0.1) parameters.lineWidth = 1;
- }
- var edgeColor = this.color;
- if (edgeColor == "auto" || CIQ.isTransparent(edgeColor))
- edgeColor = this.stx.defaultColor;
- if (this.highlighted) {
- edgeColor = this.stx.getCanvasColor("stx_highlight_vector");
- if (lineWidth == 0.1) lineWidth = 1.1;
- }
- var fillColor = this.fillColor;
- lineWidth /=
- (Math.abs(this.sx * this.sy) * 2) / (Math.abs(this.sx) + Math.abs(this.sy));
-
- context.save();
- context.translate(x0, y0);
- context.rotate(this.radians);
- context.scale(this.sx, panel.yAxis.flipped ? -this.sy : this.sy);
-
- var subshape, point;
- var origin = {
- x: (this.dimension[0] - 1) / 2,
- y: (this.dimension[1] - 1) / 2
- };
- for (subshape = 0; subshape < this.points.length; subshape++) {
- context.beginPath();
- for (point = 0; point < this.points[subshape].length; point++) {
- var x, y, cx1, cx2, cy1, cy2;
- if (this.points[subshape][point] == "M") {
- //move
- x = this.points[subshape][++point] - origin.x;
- y = this.points[subshape][++point] - origin.y;
- context.moveTo(x, y);
- } else if (this.points[subshape][point] == "L") {
- //line
- x = this.points[subshape][++point] - origin.x;
- y = this.points[subshape][++point] - origin.y;
- context.lineTo(x, y);
- } else if (this.points[subshape][point] == "Q") {
- //quadratic
- cx1 = this.points[subshape][++point] - origin.x;
- cy1 = this.points[subshape][++point] - origin.y;
- x = this.points[subshape][++point] - origin.x;
- y = this.points[subshape][++point] - origin.y;
- context.quadraticCurveTo(cx1, cy1, x, y);
- } else if (this.points[subshape][point] == "B") {
- //bezier
- cx1 = this.points[subshape][++point] - origin.x;
- cy1 = this.points[subshape][++point] - origin.y;
- cx2 = this.points[subshape][++point] - origin.x;
- cy2 = this.points[subshape][++point] - origin.y;
- x = this.points[subshape][++point] - origin.x;
- y = this.points[subshape][++point] - origin.y;
- context.bezierCurveTo(cx1, cy1, cx2, cy2, x, y);
- }
- }
- context.closePath();
-
- if (fillColor && !CIQ.isTransparent(fillColor) && fillColor != "auto") {
- //context.globalAlpha=0.4;
- context.fillStyle = fillColor;
- context.fill();
- //context.globalAlpha=1;
- }
- if (edgeColor && this.pattern != "none") {
- context.strokeStyle = edgeColor;
- context.lineWidth = lineWidth;
- if (context.setLineDash) {
- context.setLineDash(CIQ.borderPatternToArray(lineWidth, this.pattern));
- context.lineDashOffset = 0; //start point in array
- }
- context.stroke();
- }
- }
-
- //context.strokeRect(-(this.dimension[0]-1)/2,-(this.dimension[1]-1)/2,this.dimension[0]-1,this.dimension[1]-1);
-
- context.restore();
- context.save();
- context.translate(x0, y0);
- context.rotate(this.radians);
-
- if (this.highlighted) {
- var p0Fill = this.highlighted == "p0" ? true : false;
- var p1Fill = this.highlighted == "p1" ? true : false;
- var p2Fill = this.highlighted == "p2" ? true : false;
- this.littleCircle(context, 0, 0, p0Fill);
- this.mover(context, 0, 0, p0Fill);
- this.littleCircle(
- context,
- (this.sx * this.dimension[0]) / 2,
- (this.sy * this.dimension[1]) / 2,
- p1Fill
- );
- this.resizer(
- context,
- (this.sx * this.dimension[0]) / 2,
- (this.sy * this.dimension[1]) / 2,
- p1Fill
- );
- this.littleCircle(context, (this.sx * this.dimension[0]) / 2, 0, p2Fill);
- this.rotator(context, (this.sx * this.dimension[0]) / 2, 0, p2Fill);
- context.globalAlpha = 0.5;
- context.fillStyle = "#000000";
- if (this.textMeasure) {
- context.fillText(
- this.sx + "x," + this.sy + "x",
- (this.sx * this.dimension[0]) / 2 + 12,
- (this.sy * this.dimension[1]) / 2 + 5
- );
- context.fillText(
- this.a + "\u00b0",
- (this.sx * this.dimension[0]) / 2 + 12,
- 5
- );
- }
- context.globalAlpha = 1;
- } else if (this.penDown) {
- if (this.rotating) {
- this.rotator(context, (this.sx * this.dimension[0]) / 2, 0, true);
- } else {
- this.resizer(
- context,
- (this.sx * this.dimension[0]) / 2,
- (this.sy * this.dimension[1]) / 2,
- true
- );
- }
- }
- context.restore();
-};
-
-CIQ.Drawing.shape.prototype.reposition = function (
- context,
- repositioner,
- tick,
- value
-) {
- if (!repositioner) return;
- var panel = this.stx.panels[this.panelName];
- if (repositioner.action == "move") {
- var tickDiff = repositioner.tick - tick;
- var valueDiff = repositioner.value - value;
- this.setPoint(
- 0,
- repositioner.p0[0] - tickDiff,
- repositioner.p0[1] - valueDiff,
- panel.chart
- );
- this.render(context);
- } else {
- var x0 = this.stx.pixelFromTick(this.p0[0], panel.chart);
- var y0 = this.stx.pixelFromValueAdjusted(panel, this.p0[0], this.p0[1]);
- var x1 = this.stx.pixelFromTick(tick, panel.chart);
- var y1 = this.stx.pixelFromValueAdjusted(panel, tick, value);
- if (repositioner.action == "scale") {
- this[repositioner.point] = [tick, value];
- this.sx = parseFloat(
- (
- ((x1 - x0) * Math.cos(this.radians) +
- (y1 - y0) * Math.sin(this.radians)) /
- (this.dimension[0] / 2)
- ).toFixed(1)
- );
- if (Math.abs(this.sx) < 1) this.sx /= Math.abs(this.sy);
- this.sy = parseFloat(
- (
- ((y1 - y0) * Math.cos(this.radians) -
- (x1 - x0) * Math.sin(this.radians)) /
- (this.dimension[1] / 2)
- ).toFixed(1)
- );
- if (Math.abs(this.sy) < 1) this.sy /= Math.abs(this.sy);
- this.render(context);
- } else if (repositioner.action == "rotate") {
- this[repositioner.point] = [tick, value];
- this.radians = Math.atan((y1 - y0) / (x1 - x0));
- if (x1 < x0) this.radians += Math.PI;
- else if (y1 < y0) this.radians += 2 * Math.PI;
- this.a = parseInt(((this.radians * 36) / Math.PI).toFixed(0), 10) * 5;
- if (this.sx < 0) this.a = this.a + 180;
- this.a %= 360;
- this.radians = (this.a * Math.PI) / 180;
- this.render(context);
- }
- }
-};
-
-CIQ.Drawing.shape.prototype.intersected = function (tick, value, box) {
- if (!this.p0) return null; // in case invalid drawing (such as from panel that no longer exists)
- if (
- this.stx.repositioningDrawing == this &&
- this.stx.repositioningDrawing.repositioner
- )
- return this.stx.repositioningDrawing.repositioner;
-
- var panel = this.stx.panels[this.panelName];
- var x0 = this.stx.pixelFromTick(this.p0[0], panel.chart);
- var y0 = this.stx.pixelFromValueAdjusted(panel, this.p0[0], this.p0[1]);
- var x1 = this.stx.pixelFromTick(tick, panel.chart);
- var y1 = this.stx.pixelFromValueAdjusted(panel, tick, value);
-
- x1 -= x0;
- y1 -= y0;
- var y1t = y1,
- x1t = x1;
- x1 = Math.cos(this.radians) * x1t + Math.sin(this.radians) * y1t;
- y1 = Math.cos(this.radians) * y1t - Math.sin(this.radians) * x1t;
- x1 /= this.sx;
- y1 /= this.sy;
- this.padding = CIQ.ensureDefaults(this.padding || {}, {
- left: 0,
- right: 0,
- top: 0,
- bottom: 0
- });
- var paddingX = this.padding.right + this.padding.left,
- paddingY = this.padding.bottom + this.padding.top;
- var circleR2 = Math.pow(
- CIQ.touchDevice ? 25 : 5 + this.littleCircleRadius(),
- 2
- );
- var scaledCircleR2 = Math.abs(circleR2 / (this.sx * this.sy));
- var extraPaddingToIncludeScalingControls = 3;
- var overShape =
- Math.pow(
- (this.dimension[0] - paddingX + extraPaddingToIncludeScalingControls) / 2,
- 2
- ) +
- Math.pow(
- (this.dimension[1] - paddingY + extraPaddingToIncludeScalingControls) /
- 2,
- 2
- ) >
- Math.pow(x1 - paddingX / 2, 2) + Math.pow(y1 - paddingY / 2, 2);
- var moveProximity =
- (circleR2 - (Math.pow(x1 * this.sx, 2) + Math.pow(y1 * this.sy, 2))) /
- Math.abs(this.sx * this.sy);
- var scaleProximity =
- scaledCircleR2 -
- Math.pow(x1 - this.dimension[0] / 2, 2) -
- Math.pow(y1 - this.dimension[1] / 2, 2);
- var rotateProximity =
- scaledCircleR2 - Math.pow(x1 - this.dimension[0] / 2, 2) - Math.pow(y1, 2);
- //console.log("s:"+scaleProximity+" r:"+rotateProximity+" m:"+moveProximity);
- if (overShape) {
- if (
- scaleProximity >= rotateProximity &&
- scaleProximity >= moveProximity &&
- scaleProximity > -1
- ) {
- this.highlighted = "p1";
- return {
- action: "scale"
- };
- }
- if (
- rotateProximity >= scaleProximity &&
- rotateProximity >= moveProximity &&
- rotateProximity > -1
- ) {
- this.highlighted = "p2";
- return {
- action: "rotate"
- };
- }
-
- this.highlighted = moveProximity > -1 ? "p0" : true;
- return {
- action: "move",
- p0: CIQ.clone(this.p0),
- tick: tick,
- value: value
- };
- }
- return null;
-};
-
-CIQ.Drawing.shape.prototype.configs = [
- "color",
- "fillColor",
- "lineWidth",
- "pattern"
-];
-
-CIQ.Drawing.shape.prototype.littleCircleRadius = function () {
- return 3;
-};
-
-CIQ.Drawing.shape.prototype.click = function (context, tick, value) {
- if (!this.points.length) return false;
- this.copyConfig();
- var panel = this.stx.panels[this.panelName];
- if (!this.penDown) {
- this.setPoint(0, tick, value, panel.chart);
- this.penDown = true;
- return false;
- }
- //if(this.accidentalClick(tick, value)) return this.dragToDraw;
-
- this.setPoint(1, tick, value, panel.chart);
-
- if (this.rotating || !this.setRotationOnInitialDraw) {
- this.penDown = false;
- this.rotating = false;
- return true; // kernel will call render after this
- }
- this.rotating = true;
- return false;
-};
-
-CIQ.Drawing.shape.prototype.adjust = function () {
- var panel = this.stx.panels[this.panelName];
- if (!panel) return;
-
- // this section deals with backwards compatibility
- var compatibilityShapeName = this.name + "_v" + (this.version || 0);
- if (CIQ.Drawing[compatibilityShapeName]) {
- var oldShape = new CIQ.Drawing[compatibilityShapeName]();
- this.name = oldShape.name;
- this.dimension = oldShape.dimension;
- this.padding = oldShape.padding;
- this.points = oldShape.points;
- this.version = oldShape.version;
- }
-
- this.setPoint(0, this.d0, this.v0, panel.chart);
- this.radians = (Math.round(this.a / 5) * Math.PI) / 36;
-};
-
-/**
- * Reconstruct a shape
- * @param {CIQ.ChartEngine} stx The chart object
- * @param {object} [obj] A drawing descriptor
- * @param {string} [obj.col] The border color
- * @param {string} [obj.fc] The fill color
- * @param {string} [obj.pnl] The panel name
- * @param {string} [obj.ptrn] Pattern for line "solid","dotted","dashed". Defaults to solid.
- * @param {number} [obj.lw] Line width. Defaults to 1.
- * @param {number} [obj.v0] Value (price) for the center point
- * @param {number} [obj.d0] Date (string form) for the center point
- * @param {number} [obj.tzo0] Offset of UTC from d0 in minutes
- * @param {number} [obj.a] Angle of the rotation in degrees
- * @param {number} [obj.sx] Horizontal scale factor
- * @param {number} [obj.sy] Vertical scale factor
- * @memberOf CIQ.Drawing.shape
- */
-CIQ.Drawing.shape.prototype.reconstruct = function (stx, obj) {
- this.stx = stx;
- this.color = obj.col;
- this.fillColor = obj.fc;
- this.panelName = obj.pnl;
- this.pattern = obj.ptrn;
- this.lineWidth = obj.lw;
- this.d0 = obj.d0;
- this.v0 = obj.v0;
- this.tzo0 = obj.tzo0;
- this.a = obj.a;
- this.sx = obj.sx;
- this.sy = obj.sy;
- this.version = obj.ver;
- this.adjust();
-};
-
-CIQ.Drawing.shape.prototype.serialize = function () {
- return {
- name: this.name,
- pnl: this.panelName,
- col: this.color,
- fc: this.fillColor,
- ptrn: this.pattern,
- lw: this.lineWidth,
- d0: this.d0,
- v0: this.v0,
- tzo0: this.tzo0,
- a: this.a,
- sx: this.sx,
- sy: this.sy,
- ver: this.version
- };
-};
-
-/* Drawing specific shapes
- *
- * this.dimension: overall dimension of shape as designed, as a pair [dx,dy] where dx is length and dy is width, in pixels
- * this.points: array of arrays. Each array represents a closed loop subshape.
- * within each array is a series of values representing coordinates.
- * For example, ["M",0,0,"L",1,1,"L",2,1,"Q",3,3,4,1,"B",5,5,0,0,3,3]
- * The array will be parsed by the render function:
- * "M" - move to the xy coordinates represented by the next 2 array elements
- * "L" - draw line to xy coordinates represented by the next 2 array elements
- * "Q" - draw quadratic curve where next 2 elements are the control point and following 2 elements are the end coordinates
- * "B" - draw bezier curve where next 2 elements are first control point, next 2 elements are second control point, and next 2 elements are the end coordinates
- * See sample shapes below.
- *
- */
-
-CIQ.Drawing.arrow = function () {
- this.name = "arrow";
- this.version = 1;
- this.dimension = [11, 22];
- this.padding = {
- left: 0,
- right: 0,
- top: 11,
- bottom: 0
- };
- this.points = [
- [
- "M", 3, 21,
- "L", 7, 21,
- "L", 7, 16,
- "L", 10, 16,
- "L", 5, 11,
- "L", 0, 16,
- "L", 3, 16,
- "L", 3, 21
- ]
- ]; // prettier-ignore
-};
-CIQ.inheritsFrom(CIQ.Drawing.arrow, CIQ.Drawing.shape);
-
-/**
- * Function to determine which drawing tools are available.
- * @param {object} excludeList Exclusion list of tools in object form ( e.g. {"vertical":true,"annotation":true})
- * @returns {object} Map of tool names and types
- * @memberof CIQ.Drawing
- * @since 3.0.0
- */
-CIQ.Drawing.getDrawingToolList = function (excludeList) {
- var map = {};
- var excludedDrawings = {
- arrow_v0: true,
- BaseTwoPoint: true,
- fibonacci: true,
- shape: true
- };
- CIQ.extend(excludedDrawings, excludeList);
- for (var drawing in CIQ.Drawing) {
- if (!excludedDrawings[drawing] && CIQ.Drawing[drawing].prototype.render)
- map[new CIQ.Drawing[drawing]().name] = drawing;
- }
- return map;
-};
-
-};
-
-
-let __js_standard_easeMachine_ = (_exports) => {
-
-/* global _CIQ, _timezoneJS, _SplinePlotter */
-
-var CIQ = typeof _CIQ !== "undefined" ? _CIQ : _exports.CIQ;
-
-/**
- * A simple device to make ease functions easy to use. Requests a cubic function that takes the
- * form `function (t, b, c, d)`, where:
- * - t = current time
- * - b = starting value
- * - c = change in value
- * - d = duration
- *
- * @param {function} fc The cubic function.
- * @param {number} ms Milliseconds to perform the function.
- * @param {(Object.
- * Calls deleteHighlighted() which calls rightClickOverlay() for studies.
- * @memberof CIQ.ChartEngine.AdvancedInjectable#
- * @alias rightClickHighlighted
- * @example
- * stxx.prepend("rightClickHighlighted", function(){
- * console.log('do nothing on right click');
- * return true;
- * });
- */
-CIQ.ChartEngine.prototype.rightClickHighlighted = function () {
- if (this.runPrepend("rightClickHighlighted", arguments)) return;
- this.deleteHighlighted(true);
- this.runAppend("rightClickHighlighted", arguments);
-};
-
-/**
- * INJECTABLE
- *
- * Removes any and all highlighted overlays, series or drawings.
- *
- * @param {boolean} callRightClick When true, call the right click method for the given highlight:
- * - Drawing highlight calls {@link CIQ.ChartEngine.AdvancedInjectable#rightClickDrawing}
- * - Overlay study highlight calls {@link CIQ.ChartEngine.AdvancedInjectable#rightClickOverlay}
- * @param {boolean} forceEdit Skip the context menu and begin editing immediately, usually for
- * touch devices.
- *
- * @memberof CIQ.ChartEngine.AdvancedInjectable#
- * @alias deleteHighlighted
- * @since
- * - 4.1.0 Removes a renderer from the chart if it has no series attached to it.
- * - 6.2.0 Calls {@link CIQ.ChartEngine.AdvancedInjectable#rightClickDrawing} when a drawing is
- * highlighted and the `callRightClick` paramenter is true.
- */
-CIQ.ChartEngine.prototype.deleteHighlighted = function (
- callRightClick,
- forceEdit
-) {
- if (this.runPrepend("deleteHighlighted", arguments)) return;
- this.cancelTouchSingleClick = true;
- CIQ.clearCanvas(this.chart.tempCanvas, this);
- var canDeleteAll = this.bypassRightClick === false;
- if (canDeleteAll || !this.bypassRightClick.drawing) {
- for (var i = this.drawingObjects.length - 1; i >= 0; i--) {
- var drawing = this.drawingObjects[i];
-
- if (!drawing.highlighted) continue;
-
- if (callRightClick) {
- this.rightClickDrawing(drawing, forceEdit);
- } else if (!drawing.permanent) {
- var dontDeleteMe = drawing.abort();
- if (!dontDeleteMe) {
- var before = this.exportDrawings();
- this.drawingObjects.splice(i, 1);
- this.undoStamp(before, this.exportDrawings());
- }
- this.changeOccurred("vector");
- }
- }
- }
- if (canDeleteAll || !this.bypassRightClick.study) {
- for (var name in this.overlays) {
- var o = this.overlays[name];
- if ((o.overlay || o.underlay) && o.highlight && !o.permanent) {
- if (callRightClick || forceEdit)
- this.rightClickOverlay(name, forceEdit);
- else this.removeOverlay(name);
- }
- }
- }
-
- var chart = this.currentPanel.chart;
- if (canDeleteAll || !this.bypassRightClick.series) {
- for (var r in chart.seriesRenderers) {
- var renderer = chart.seriesRenderers[r];
- if (renderer.params.highlightable) {
- var rPanel = this.panels[renderer.params.panel];
- var yAxisName = rPanel && rPanel.yAxis.name;
- for (var sp = renderer.seriesParams.length - 1; sp >= 0; sp--) {
- var series = renderer.seriesParams[sp];
- if (
- (renderer.params.highlight || series.highlight) &&
- !series.permanent
- ) {
- renderer.removeSeries(series.id);
- if (renderer.seriesParams.length < 1) {
- this.removeSeriesRenderer(renderer);
- if (renderer.params.name == yAxisName) {
- this.electNewPanelOwner(renderer.params.panel);
- } else {
- this.checkForEmptyPanel(renderer.params.panel);
- var rendererAxis = this.getYAxisByName(
- rPanel,
- renderer.params.name
- );
- if (rendererAxis) {
- rendererAxis.name =
- rendererAxis.studies[0] || rendererAxis.renderers[1];
- }
- }
- }
- }
- }
- }
- }
- }
-
- this.draw();
- this.resizeChart();
- this.clearMeasure();
- var mSticky = this.controls.mSticky;
- if (mSticky) {
- mSticky.style.display = "none";
- mSticky.children[0].innerHTML = "";
- }
- this.runAppend("deleteHighlighted", arguments);
-};
-
-/**
- * Displays the "ok to drag" div and the study/series which is highlighted, near the crosshairs.
- * @param {boolean} [soft] True to just set the position of an already displayed div, otherwise, toggles display style based on whether long press was completed.
- * @memberof CIQ.ChartEngine
- * @since 7.1.0
- */
-CIQ.ChartEngine.prototype.displayDragOK = function (soft) {
- function showText(control) {
- var text = this.translateIf(
- control.querySelector(".field").getAttribute("text")
- );
- var hoveredYAxis = this.whichYAxis(
- this.whichPanel(this.cy),
- this.cx,
- this.cy
- );
- if (hoveredYAxis && hoveredYAxis.dropzone == "all") {
- text += "-->" + this.translateIf(hoveredYAxis.name);
- }
- control.querySelector(".field").innerHTML = text;
- }
- var dragControl = this.controls.dragOk;
- if (dragControl) {
- if (!soft) {
- if (
- !this.tapForHighlighting ||
- !this.touchingEvent ||
- this.anyHighlighted
- )
- this.findHighlights(this.highlightViaTap); // trigger highlighting
- }
- var draggableObject = this.highlightedDraggable; // set by findHighlights
- var dragNotAllowed =
- draggableObject &&
- draggableObject.undraggable &&
- draggableObject.undraggable(this);
- var cx = this.cx,
- cy = this.cy;
- if (!soft) {
- if (
- draggableObject &&
- !dragNotAllowed &&
- this.longHoldTookEffect &&
- !this.cancelLongHold
- ) {
- var baseText =
- (draggableObject.inputs && draggableObject.inputs.display) ||
- (draggableObject.params &&
- (draggableObject.params.display || draggableObject.params.name)) ||
- draggableObject.name;
- dragControl.querySelector(".field").setAttribute("text", baseText);
- showText.call(this, dragControl);
- dragControl.style.display = "inline-block";
- this.draw(); // trigger opacity change
- this.displaySticky();
- if (this.grabStartYAxis)
- this.container.classList.replace("stx-drag-chart", "stx-drag-axis");
- else
- this.container.classList.replace("stx-drag-chart", "stx-drag-series");
- } else {
- dragControl.style.display = "none";
- this.draw();
- this.container.classList.remove("stx-drag-series");
- this.container.classList.remove("stx-drag-axis");
- for (var panel in this.panels) {
- var classList = this.panels[panel].subholder.classList;
- classList.remove("dropzone"); // IE 11 won't let you pass multiple classes
- classList.remove("all");
- classList.remove("left");
- classList.remove("right");
- classList.remove("top");
- classList.remove("bottom");
- var y;
- for (y = 0; y < this.panels[panel].yaxisLHS.length; y++) {
- this.panels[panel].yaxisLHS[y].dropzone = null;
- }
- for (y = 0; y < this.panels[panel].yaxisRHS.length; y++) {
- this.panels[panel].yaxisRHS[y].dropzone = null;
- }
- }
- }
- this.draw();
- }
- if (draggableObject) {
- var top = cy + dragControl.offsetHeight;
- var left = Math.max(0, cx - dragControl.offsetWidth);
- dragControl.style.top = top + "px";
- dragControl.style.left = left + "px";
- showText.call(this, dragControl);
- }
- }
-};
-
-/**
- * Displays the "ok to draw" icon and the field which is highlighted, near the crosshairs. Used with the [average line drawing]{@link CIQ.Drawing.average}.
- *
- * In general, any series and most studies can have an average line drawing placed on it.
- * When such a plot is highlighted, this function will show the [drawOk chart control]{@link CIQ.ChartEngine.htmlControls} and display the field being highlighted.
- * @memberof CIQ.ChartEngine
- * @since 7.0.0
- */
-CIQ.ChartEngine.prototype.displayDrawOK = function () {
- var drawable = this.controls.drawOk;
- if (drawable && CIQ.Drawing) {
- var drawing = CIQ.Drawing[this.currentVectorParameters.vectorType];
- if (drawing) drawing = new drawing();
- if (this.highlightedDataSetField && drawing && drawing.getYValue) {
- drawable.style.display = "inline-block";
- var top = this.cy + drawable.offsetHeight;
- var left = this.cx - drawable.offsetWidth;
- drawable.style.top = top + "px";
- drawable.style.left = left + "px";
- drawable.querySelector(".field").innerHTML = this.translateIf(
- this.highlightedDataSetField
- );
- } else drawable.style.display = "none";
- }
-};
-
-/**
- * INJECTABLE
- *
- * Zooms (vertical swipe / mousewheel) or pans (horizontal swipe) the chart based on a mousewheel event.
- *
- * Uses for following for zooming:
- * - {@link CIQ.ChartEngine#zoomIn}
- * - {@link CIQ.ChartEngine#zoomOut}
- *
- * Uses the following for panning:
- * - {@link CIQ.ChartEngine#mousemoveinner}
- *
- * Circumvented if:
- * - {@link CIQ.ChartEngine#allowZoom} is set to `false`
- * - {@link CIQ.ChartEngine#captureMouseWheelEvents} is set to `false`
- * - on a vertical swipe and {@link CIQ.ChartEngine#allowSideswipe} is `false`
- *
- * See the following options:
- * - {@link CIQ.ChartEngine#reverseMouseWheel}
- * - {@link CIQ.ChartEngine#mouseWheelAcceleration}
- *
- * @param {Event} e The event
- * @return {boolean} Returns false if action is taken
- * @memberof CIQ.ChartEngine.AdvancedInjectable#
- * @alias mouseWheel
- */
-CIQ.ChartEngine.prototype.mouseWheel = function (e) {
- if (this.runPrepend("mouseWheel", arguments)) return;
- if (e.preventDefault) e.preventDefault();
- if (this.openDialog !== "") return; // don't zoom when dialog or menu is open
- var deltaX = e.deltaX,
- deltaY = e.deltaY;
-
- /*
- // OSX trackpad is very sensitive since it accommodates diagonal
- // motion which is not relevant to us. So we ignore any changes
- // in direction below the threshold time value
- var threshold=50; //ms
- if(Date.now()-this.lastMouseWheelEvent
Available options are:
- * - "circle"
- * - "square"
- * - "callout"
- * @param {string} params.headline The headline text to pop-up when clicked.
- * @param {string} [params.category] The category class to add to your marker.
- *
Available options are:
- * - "news"
- * - "earningsUp"
- * - "earningsDown"
- * - "dividend"
- * - "filing"
- * - "split"
- * @param {string} [params.story] The story to pop-up when clicked.
- * @example
- * var datum = {
- * type: "circle",
- * headline: "This is a Marker for a Split",
- * category: "split",
- * story: "This is the story of a split"
- * };
- *
- * var mparams = {
- * stx: stxx,
- * label: "Sample Events",
- * xPositioner: "date",
- * x: aDate,
- * node: new CIQ.Marker.Simple(datum)
- * };
- *
- * var marker = new CIQ.Marker(mparams);
- */
- CIQ.Marker.Simple = function (params) {
- var node = (this.node = document.createElement("div"));
- node.className = "stx-marker";
- node.classList.add(params.type);
- if (params.category) node.classList.add(params.category);
- var visual = CIQ.newChild(node, "div", "stx-visual");
- CIQ.newChild(node, "div", "stx-stem");
-
- var expand;
- if (params.type == "callout") {
- var content = CIQ.newChild(visual, "div", "stx-marker-content");
- CIQ.newChild(content, "h4", null, params.headline);
- expand = CIQ.newChild(content, "div", "stx-marker-expand");
- CIQ.newChild(expand, "p", null, params.story);
- } else {
- expand = CIQ.newChild(node, "div", "stx-marker-expand");
- CIQ.newChild(expand, "h4", null, params.headline);
- CIQ.newChild(expand, "p", null, params.story);
- CIQ.safeClickTouch(expand, function (e) {
- node.classList.toggle("highlight");
- });
- }
-
- function cb() {
- CIQ.Marker.positionContentVerticalAndHorizontal(node);
- }
- CIQ.safeClickTouch(visual, function (e) {
- node.classList.toggle("highlight");
- setTimeout(cb, 10);
- });
- this.nodeType = "Simple";
- this.expand = expand;
- };
-
- CIQ.inheritsFrom(CIQ.Marker.Simple, CIQ.Marker.NodeCreator, false);
-}
-
-};
-
-// @jscrambler disable functionOutlining
-let __js_standard_market_ = (_exports) => {
-
-/* global _CIQ, _timezoneJS, _SplinePlotter */
-
-var CIQ = typeof _CIQ !== "undefined" ? _CIQ : _exports.CIQ;
-var timezoneJS =
- typeof _timezoneJS !== "undefined" ? _timezoneJS : _exports.timezoneJS;
-
-var HOUR_MILLIS = 60000 * 60;
-var DAY_MILLIS = HOUR_MILLIS * 24;
-
-var ExistingMarket = CIQ.Market;
-
-/**
- * The market class is what the chart uses to to manage market hours for the different exchanges.
- * It uses `Market Definitions` to decide when the market is open or closed.
- * Although you can construct many market classes with different definitions to be used in your functions, only one market definition can be attached to the chart at any given time.
- * Once a market is defined, an [iterator]{@link CIQ.Market#newIterator} can be created to traverse through time, taking into account the market hours.
- * Additionally, a variety of convenience functions can be used to check the market status, such as {@link CIQ.Market#isOpen} or {@link CIQ.Market#isMarketDay}.
- *
- * A chart will operate 24x7, unless a market definition with rules is assigned to it.
- * See {@link CIQ.ChartEngine#setMarket} and {@link CIQ.ChartEngine#setMarketFactory} for instructions on how to assign a market definition to a chart.
- *
- * The chart also provides convenience functions that allows you to traverse through time at the current chart periodicity without having to explicitly create a new iterator.
- * See {@link CIQ.ChartEngine#getNextInterval} and {@link CIQ.ChartEngine#standardMarketIterator} for details.
- *
- * **Important:**
- * - If the {@link CIQ.ExtendedHours} visualization and filtering add-on is enabled, **only data within the defined market hours will be displayed on the chart** even if more data is loaded.
- * - Once a market definition is assigned to a chart, it will be used to roll up any data requested by the [periodicity]{@link CIQ.ChartEngine#createDataSet}, which will result in any data outside the market hours to be combined with the prior candle.
- * This may at times look like data is being **filtered**, but it is just being **aggregated**. To truly filter data, you must use the above add-on.
- *
- * `Market Definitions` are JavaScript objects which must contain the following elements:
- * - `name` : A string. Name of the market for which the rules are for.
- * - `rules` : An array. The rules indicating the times the market is open or closed. `close` time **must always** be later than `open` time. Use the proper market timezone (`market_tz`) to prevent hours from spanning across days.
- * - `market_tz` : A string. Time zone in which the market operates. See {@link CIQ.timeZoneMap} to review a list of all chartIQ supported timezones and instructions on how to add more.
- * - `hour_aligned`: A boolean. If set to `true`, market opening and closing times will be forced to the exact start of the hour of time, ignoring any minutes, seconds or millisecond offsets.
- * > You should set this to `false` if your market opening and closing times are not aligned to the beginning to each hour.
- * > Otherwise, forcing them to do so causes the iterator to generate `previous` and `next` times that could prevent it from properly moving trough the market hours.
- * - `convertOnDaily` : A boolean. By default, daily charts are not converted for timezone. Set this to true to convert for daily charts.
- * - `beginningDayOfWeek` : Weekday number (0-6) to optionally override CIQ.Market prototype setting of same name.
- * - `normal_daily_open`: A string defining a time in `HH:mm` format. Set this to specify the normal open time for a market.
- * - `normal_daily_close`: A string defining a time in `HH:mm` format. Set this to specify the normal close time for a market.
- *
- * Example:
- * ```
- * {
- * name: "SAMPLE-MARKET",
- * market_tz: "America/Chicago",
- * hour_aligned: true,
- * beginningDayOfWeek: 0,
- * normal_daily_open: "09:00",
- * normal_daily_close: "17:00",
- * rules: [
- * {"dayofweek": 1, "open": "09:00", "close": "17:00"}
- * ]
- * };
- * ```
- *
- * Instructions for creating `Market Definitions`:
- *
- * - An empty market definition ( {} ) assumes the market is always open.
- * - Once a definition has rules in it, the market will be assumed open only for those defined rules. The absence of a rule indicates the market is closed for that timeframe.
- * - Market's time rules are specified in the market's local timezone.
- * - Seconds are not considered for open or close times, but are okay for intra day data.
- * - Rules are processed top to bottom.
- * - Rules can be defined for both primary and secondary market sessions.
- * - Rules for the market's primary session do not have a `name` parameter and are enabled by default.
- * - Rules for the market's primary session are mandatory.
- * - Rules for secondary market sessions, such as pre-market or post-market trading hours sessions, require a `name` parameter.
- * - All secondary market session are disabled by default.
- *
- * This is a rule for a 'pre' market session:
- * `{"dayofweek": 1, "open": "08:00", "close": "09:30", name: "pre"}`
- *
- * - To enable or disable secondary market session rules by session name, use {@link CIQ.Market#enableSession} and {@link CIQ.Market#disableSession}.
- * - **Important:** Enabling/Disabling market sessions will not automatically filter-out data from the chart, but simply adjust the market iterators so the x-axis can be displayed accordingly in the absence of data for the excluded sessions.
- * - Data filtering can be done:
- * - Manually by requesting pertinent data from your feed and calling {@link CIQ.ChartEngine#loadChart}
- * - Automatically by using the {@link CIQ.ExtendedHours} visualization and filtering add-on.
- * - First, the `dayofweek` wild card rules are processed. As soon as a rule is matched, processing breaks.
- *
- * This rule says the market is open every Monday from 9:30 to 16:00:
- * `{"dayofweek": 1, "open": "09:30", "close": "16:00"}`
- *
- * - After the `dayofweek` rules are processed all of the extra rules are processed.
- * - Multiple `open` and `close` times can be set for the same day of week. To indicate the market is closed during lunch, for example:
- * ```
- * {"dayofweek": 1, "open": "09:00", "close": "12:00"}, // mon
- * {"dayofweek": 1, "open": "13:00", "close": "17:00"} // mon
- * ```
- * - `close` time **must always** be later than `open` time.
- * - Use the proper market timezone (`market_tz`) to prevent hours from spanning across days.
- *
- * - Wildcard rules should be placed first and more specific rules should be placed later.
- *
- * This rule is a wildcard rule for Christmas. If Christmas is on Monday, the
- * first set of rules will evaluate to true because the dayofweek rule for day
- * one will match. Then this rule will match if the date is the 25th of
- * December in any year. Because open is 00:00 and close is 00:00, it will evaluate to false:
- * `{"date": "*-12-25", "open": "00:00", "close": "00:00"}`
- *
- * - After wildcard exceptions, any specific day and time can be matched.
- *
- * This rule says closed on this day only. Note that open and closed attributes
- * can be omitted to save typing if the market is closed the entire day:
- * `{"date": "2016-01-18"} //Martin Luther King day.`
- *
- * This rules says closed on 12-26:
- * `{"date": "2016-12-26"}, //Observed Christmas in 2016`
- *
- * This rule says partial session
- * `{"date": "2015-12-24", "open": "9:30", "close": "13:00"} //Christmas eve`
- *
- * See example section for a compete NYSE definition.
- *
- * Once defined, it can be used to create a new market instance.
- *
- * Example:
- * ```
- * var thisMarket = new CIQ.Market(marketDefinition);
- * ```
- *
- * If no definition is provided, the market will operate 24x7.
- *
- * Example:
- * ```
- * new CIQ.Market();
- * ```
- *
- * @param {object} [market_definition] A json object that contains the rules for some market. If not defined default market is always open.
- *
- * @constructor
- * @name CIQ.Market
- * @since
- *
04-2016-08
- *
06-2016-02 - You can now specify times for different market sessions ('pre',post', etc) to be used with the sessions visualization tools. See {@link CIQ.ExtendedHours}.
- *
- * @example
- * CIQ.Market.NYSE = {
- "name": "NYSE",
- "market_tz": "America/New_York",
- "hour_aligned": false,
- "rules": [
- //First open up the regular trading times
- //Note that sat and sun (in this example) are always closed because
- //everything is closed by default and we didn't explicitly open them.
- {"dayofweek": 1, "open": "09:30", "close": "16:00"}, //mon
- {"dayofweek": 2, "open": "09:30", "close": "16:00"},
- {"dayofweek": 3, "open": "09:30", "close": "16:00"},
- {"dayofweek": 4, "open": "09:30", "close": "16:00"},
- {"dayofweek": 5, "open": "09:30", "close": "16:00"}, //fri
-
- //After Hours premarket
- {"dayofweek": 1, "open": "08:00", "close": "09:30", name: "pre"}, //mon
- {"dayofweek": 2, "open": "08:00", "close": "09:30", name: "pre"},
- {"dayofweek": 3, "open": "08:00", "close": "09:30", name: "pre"},
- {"dayofweek": 4, "open": "08:00", "close": "09:30", name: "pre"},
- {"dayofweek": 5, "open": "08:00", "close": "09:30", name: "pre"}, //fri
-
- //After Hours post
- {"dayofweek": 1, "open": "16:00", "close": "20:00", name: "post"}, //mon
- {"dayofweek": 2, "open": "16:00", "close": "20:00", name: "post"},
- {"dayofweek": 3, "open": "16:00", "close": "20:00", name: "post"},
- {"dayofweek": 4, "open": "16:00", "close": "20:00", name: "post"},
- {"dayofweek": 5, "open": "16:00", "close": "20:00", name: "post"}, //fri
-
- //Now Monday thru Friday is open. Close any exceptions
-
- //always closed on Christmas
- {"date": "*-12-25", "open": "00:00", "close": "00:00"},
-
- //always closed on 4th of July
- {"date": "*-07-04", "open": "00:00", "close": "00:00"},
-
- //always close on new years day
- {"date": "*-01-01", "open": "00:00", "close": "00:00"},
-
- //Some holidays are observed on different days each year or if
- //the day falls on a weekend. Each of those rules must be specified.
- {"date": "2012-01-02", "open": "00:00", "close": "00:00"},
-
- //As a special case if no open and close attributes are set they
- //will be assumed "00:00" and "00:00" respectively
- {"date": "2017-01-02"},
-
- {"date": "2016-01-18"},
- {"date": "2016-02-15"},
- {"date": "2016-03-25"},
- {"date": "2016-05-30"},
- {"date": "2016-09-05"},
- {"date": "2016-11-24"},
- {"date": "2016-11-25", "open": "8:00", "close": "9:30", name: "pre"},
- {"date": "2016-11-25", "open": "9:30", "close": "13:00"},
- {"date": "2016-12-26"},
-
- {"date": "2015-01-19"},
- {"date": "2015-02-16"},
- {"date": "2015-04-03"},
- {"date": "2015-05-25"},
- {"date": "2015-07-03"},
- {"date": "2015-09-07"},
- {"date": "2015-11-26"},
- {"date": "2015-11-27", "open": "8:00", "close": "9:30", name: "pre"},
- {"date": "2015-11-27", "open": "9:30", "close": "13:00"},
- {"date": "2015-12-24", "open": "8:00", "close": "9:30", name: "pre"},
- {"date": "2015-12-24", "open": "9:30", "close": "13:00"},
-
- {"date": "2014-01-20"},
- {"date": "2014-02-17"},
- {"date": "2014-04-18"},
- {"date": "2014-05-26"},
- {"date": "2014-09-01"},
- {"date": "2014-11-27"},
- {"date": "2014-07-03", "open": "8:00", "close": "9:30", name: "pre"},
- {"date": "2014-07-03", "open": "9:30", "close": "13:00"},
- {"date": "2014-11-28", "open": "8:00", "close": "9:30", name: "pre"},
- {"date": "2014-11-28", "open": "9:30", "close": "13:00"},
- {"date": "2014-12-24", "open": "8:00", "close": "9:30", name: "pre"},
- {"date": "2014-12-24", "open": "9:30", "close": "13:00"},
-
- {"date": "2013-01-21"},
- {"date": "2013-02-18"},
- {"date": "2013-03-29"},
- {"date": "2013-05-27"},
- {"date": "2013-09-02"},
- {"date": "2013-11-28"},
- {"date": "2013-07-03", "open": "8:00", "close": "9:30", name: "pre"},
- {"date": "2013-07-03", "open": "9:30", "close": "13:00"},
- {"date": "2013-11-29", "open": "8:00", "close": "9:30", name: "pre"},
- {"date": "2013-11-29", "open": "9:30", "close": "13:00"},
- {"date": "2013-12-24", "open": "8:00", "close": "9:30", name: "pre"},
- {"date": "2013-12-24", "open": "9:30", "close": "13:00"},
-
- {"date": "2012-01-16"},
- {"date": "2012-02-20"},
- {"date": "2012-04-06"},
- {"date": "2012-05-28"},
- {"date": "2012-09-03"},
- {"date": "2012-10-29"},
- {"date": "2012-10-30"},
- {"date": "2012-11-22"},
- {"date": "2012-07-03", "open": "8:00", "close": "9:30", name: "pre"},
- {"date": "2012-07-03", "open": "9:30", "close": "13:00"},
- {"date": "2012-11-23", "open": "8:00", "close": "9:30", name: "pre"},
- {"date": "2012-11-23", "open": "9:30", "close": "13:00"},
- {"date": "2012-12-24", "open": "8:00", "close": "9:30", name: "pre"},
- {"date": "2012-12-24", "open": "9:30", "close": "13:00"}
- ]
- };
- */
-CIQ.Market = function (market_definition) {
- this.market_def = false;
- this.rules = false;
- this.normalHours = [];
- this.extraHours = [];
- this.class_name = "Market";
- if (!timezoneJS.Date) {
- this.tz_lib = Date; //needed to run unit tests
- } else {
- this.tz_lib = timezoneJS.Date;
- }
- this.market_tz = "";
- this.hour_aligned = false;
- this.convertOnDaily = false;
- this.enabled_by_default = false;
-
- //needed to run unit tests otherwise should do nothing
- if (
- typeof market_definition != "undefined" &&
- market_definition &&
- !CIQ.isEmpty(market_definition)
- ) {
- if (market_definition.market_definition) {
- market_definition = market_definition.market_definition;
- }
- if (market_definition.rules) {
- this.rules = market_definition.rules;
- }
- if (market_definition.market_tz) {
- this.market_tz = market_definition.market_tz;
- }
- if (market_definition.convertOnDaily) {
- this.convertOnDaily = market_definition.convertOnDaily;
- }
- if (typeof market_definition.hour_aligned) {
- this.hour_aligned = market_definition.hour_aligned;
- }
- if (typeof market_definition.beginningDayOfWeek !== "undefined") {
- this.beginningDayOfWeek = market_definition.beginningDayOfWeek;
- }
- if (typeof market_definition.enabled_by_default !== "undefined") {
- if (market_definition.enabled_by_default instanceof Array) {
- this.enabled_by_default = market_definition.enabled_by_default;
- }
- }
-
- this.market_def = market_definition;
- if (this.market_def.name === undefined) {
- this.market_def.name = "no market name specified";
- }
- } else {
- return;
- }
-
- CIQ.Market._createTimeSegments(this);
- this.getSessionNames();
-};
-
-/**
- * Set of rules for identifying instrument's exchange and deriving a market definition from a symbol.
- * This is only required if your chart will need to know the operating hours for the different exchanges.
- * If using a 24x7 chart, this class is not needed.
- *
- * **Default implementation can be found in examples/markets/marketDefinitionsSample.js. Please review and override the functions in there to match the symbol format of your quotefeed or results will be unpredictable.**
- *
- * @namespace
- * @name CIQ.Market.Symbology
- * @since 04-2016-08
- */
-CIQ.Market.Symbology = function () {};
-
-/**
- * Returns true if the instrument is foreign.
- *
- * **This is dependent on the market data feed and should be overridden accordingly.**
- *
- * @param {string} symbol The symbol
- * @return {boolean} True if it's a foreign symbol
- * @memberof CIQ.Market.Symbology
- * @since 04-2016-08
- * @example
- * CIQ.Market.Symbology.isForeignSymbol=function(symbol){
- * if(!symbol) return false;
- * return symbol.indexOf(".")!=-1;
- * };
- */
-CIQ.Market.Symbology.isForeignSymbol = function (symbol) {
- return false;
-};
-
-/**
- * Returns true if the instrument is a future.
- *
- * **This is dependent on the market data feed and should be overridden accordingly.**
- *
- * @param {string} symbol The symbol
- * @return {boolean} True if it's a futures symbol
- * @memberof CIQ.Market.Symbology
- * @since 04-2016-08
- * @example
- * CIQ.Market.Symbology.isFuturesSymbol=function(symbol){
- * if(!symbol) return false;
- * if(symbol.indexOf("/")!==0 || symbol=="/") return false;
- * return true;
- * };
- */
-CIQ.Market.Symbology.isFuturesSymbol = function (symbol) {
- return false;
-};
-
-/**
- * Determines whether an instrument is a rate.
- *
- * **Note:** This function is dependent on the market data feed and should be overridden accordingly.
- *
- * @param {string} symbol The symbol.
- * @return {boolean} By default, false. Override this function to return true if the symbol
- * is a rate family or rate.
- * @memberof CIQ.Market.Symbology
- * @since 7.4.0
- * @example
- * CIQ.Market.Symbology.isRateSymbol=function(symbol){
- * if(!symbol) return false;
- * if(symbol.indexOf("%")!==0 || symbol=="%") return false;
- * return true;
- * };
- */
-CIQ.Market.Symbology.isRateSymbol = function (symbol) {
- return false;
-};
-
-/**
- * Returns true if the instrument is a forex symbol.
- *
- * **This is dependent on the market data feed and should be overridden accordingly.**
- *
- * @param {string} symbol The symbol
- * @return {boolean} True if it's a forex symbol
- * @memberof CIQ.Market.Symbology
- * @since 04-2016-08
- * @example
- * CIQ.Market.Symbology.isForexSymbol=function(symbol){
- * if(!symbol) return false;
- * if(CIQ.Market.Symbology.isForeignSymbol(symbol)) return false;
- * if(CIQ.Market.Symbology.isFuturesSymbol(symbol)) return false;
- * if(symbol.length<6 || symbol.length>7) return false;
- * if(symbol.length==6 && symbol[5]=="X") return false; // This is a fund of some sort
- * if(/\^?[A-Za-z]{6}/.test(symbol)) return true;
- * return false;
- * };
- */
-CIQ.Market.Symbology.isForexSymbol = function (symbol) {
- return false;
-};
-
-/**
- * Returns true if the symbol is a metal/currency or currency/metal pair
- *
- * **This is dependent on the market data feed and should be overridden accordingly.**
- *
- * @param {string} symbol The symbol
- * @param {boolean} inverse Set to true to test specifically for a currency/metal pair (e.g. EURXAU, but not XAUEUR).
- * @return {boolean} True if it's a metal symbol
- * @memberof CIQ.Market.Symbology
- * @since 04-2016-08
- * @example
- * CIQ.Market.Symbology.isForexMetal=function(symbol,inverse){
- * var metalsSupported={
- * "XAU":true, "XAG":true, "XPT":true, "XPD":true
- * };
- * if(!symbol) return false;
- * if(!CIQ.Market.Symbology.isForexSymbol(symbol)) return false;
- * if(symbol.charAt(0)!="^") symbol="^"+symbol;
- * if(!metalsSupported[symbol.substring(1,4)] && metalsSupported[symbol.substring(4,7)]) return true;
- * else if(!inverse && metalsSupported[symbol.substring(1,4)] && !metalsSupported[symbol.substring(4,7)]) return true;
- * return false;
- * };
- */
-CIQ.Market.Symbology.isForexMetal = function (symbol, inverse) {
- return false;
-};
-
-/**
- * Returns true if the symbol is a forex or a future
- *
- * @param {string} symbol The symbol
- * @return {boolean} True if the symbol is a forex or a future
- * @memberof CIQ.Market.Symbology
- * @since 04-2016-08
- */
-CIQ.Market.Symbology.isForexFuturesSymbol = function (symbol) {
- if (CIQ.Market.Symbology.isForexSymbol(symbol)) return true;
- if (CIQ.Market.Symbology.isFuturesSymbol(symbol)) return true;
- return false;
-};
-
-/**
- * This is a function that takes a symbolObject of form accepted by {@link CIQ.ChartEngine#loadChart}, and returns a market definition.
- * When loading it with {@link CIQ.ChartEngine#setMarketFactory}, it will be used by the chart to dynamically change market definitions when a new instrument is activated.
- *
- * **Very important:**
- * Default implementation can be found in examples/markets/marketDefinitionsSample.js. Please review and override the functions in there to match the symbol format of your quotefeed or results will be unpredictable.
- *
- * See {@link CIQ.Market} for instruction on how to create a market definition.
- * @param {object} symbolObject Symbol object of form accepted by {@link CIQ.ChartEngine#loadChart}
- * @return {object} A market definition. See {@link CIQ.Market} for instructions.
- * @memberof CIQ.Market.Symbology
- * @since 04-2016-08
- * @example
- * // default implementation
- * var factory=function(symbolObject){
- * var symbol=symbolObject.symbol;
- * if(CIQ.Market.Symbology.isForeignSymbol(symbol)) return null; // 24 hour market definition
- * if(CIQ.Market.Symbology.isFuturesSymbol(symbol)) return CIQ.Market.GLOBEX;
- * if(CIQ.Market.Symbology.isForexMetal(symbol)) return CIQ.Market.METALS;
- * if(CIQ.Market.Symbology.isForexSymbol(symbol)) return CIQ.Market.FOREX;
- * return CIQ.Market.NYSE;
- * };
- */
-CIQ.Market.Symbology.factory = function (symbolObject) {
- return null; // 24 hour market definition
-};
-
-/**
- * Encodes the string identifier for a specific instrument in a term structure chart. This
- * function is called when a time series chart is opened for a term structure instrument.
- * See {@link CIQ.UI.CurveEdit.launchTimeSeries}.
- *
- * Typically, the implementation of this function concatenates the term structure entity with
- * the instrument name to fully identify the instrument on the time series chart (see example).
- *
- * Override this function to specify whatever encoding you need for your use case.
- *
- * @param {string} entity The symbol/entity for the curve; for example, "US-T BENCHMARK".
- * @param {string} instrument An individual instrument; for example, "20 YR".
- * @return {string} The symbol for the individual instrument; for example, "US-T BENCHMARK 20 YR".
- *
- * @memberof CIQ.Market.Symbology
- * @since 7.4.0
- *
- * @example
- * CIQ.Market.Symbology.encodeTermStructureInstrumentSymbol = function(entity, instrument) {
- * // Remove leading % sign.
- * if (entity[0] === "%") entity = entity.slice(1);
- * return entity + " " + instrument;
- * };
- */
-CIQ.Market.Symbology.encodeTermStructureInstrumentSymbol = function (
- entity,
- instrument
-) {
- console.warn(
- "You are trying to call `CIQ.Market.Symbology.encodeTermStructureInstrumentSymbol` but have not implemented it."
- );
-};
-
-if (ExistingMarket) CIQ.extend(CIQ.Market, ExistingMarket);
-
-/**
- * An array of objects containing information about the current market's extended sessions.
- * Each element has a name prop (for the name of the session) and an enabled prop.
- * See {@link CIQ.ExtendedHours} for more information on extended sessions.
- * @type array
- * @default
- * @alias sessions
- * @memberof CIQ.Market
- * @example
- * marketSessions=stxx.chart.market.sessions
- */
-CIQ.Market.prototype.sessions = null;
-
-/**
- * The day on which to begin a week: 0 = Sunday, 1 = Monday, ..., 6 = Saturday.
- *
- * This is a global setting, but can be overridden with a market-specific setting in the market
- * definition.
- *
- * @type {number}
- * @default 0
- * @alias beginningDayOfWeek
- * @memberof CIQ.Market#
- * @since 8.2.0
- *
- * @example
- * stxx.chart.market.beginningDayOfWeek = 5; // Start week on Friday.
- */
-CIQ.Market.prototype.beginningDayOfWeek = 0;
-
-/**
- * Returns an array of objects containing a list of sessions and whether or not they are enabled
- *
- * @return {array} String array of market session names, and corresponding status (e.g. [{ name: 'pre', enabled: false } { name: 'post', enabled: true }])
- * @since 6.0.0
- */
-CIQ.Market.prototype.getSessionNames = function () {
- if (!this.rules) {
- //Its a safe assumption this is a 24 hour chart, and that it has no sessions
- this.sessions = [];
- } else if (!this.sessions) {
- var names = [];
- var marketSessions = [];
-
- this.rules.map(function (rule) {
- if (rule.name && names.indexOf(rule.name) === -1) {
- names.push(rule.name);
-
- marketSessions.push({
- name: rule.name,
- enabled: rule.enabled ? rule.enabled : false
- });
- }
- });
-
- this.sessions = marketSessions;
- }
- return this.sessions.slice();
-};
-
-/**
- * Primitive to find the next matching time segment taking into account rules for adjacent sessions.
- * If the date lands exactly on the open or close time for a session, then it will still seek to the next market session.
- * @param {date} date A start date time in the market_tz timezone.
- * @param {boolean} open True if looking for an open time
- * @return {date} A date in the market_tz timezone that falls somewhere in a matching time segment. Probably 1 before close. Or null if no rules are defined
- * @memberof CIQ.Market
- * @since 05-2016-10
- * @private
- */
-CIQ.Market.prototype._find_next_segment = function (date, open) {
- if (!this.market_def) return null; // special case
- if (!this.rules) return null; //special case
- var d = new Date(+date);
- var iter = this.newIterator({
- begin: d,
- interval: 1,
- inZone: this.market_tz,
- outZone: this.market_tz
- });
- if (this._wasOpenIntraDay(d)) {
- var hours = this.zseg_match.close_parts.hours;
- var minutes = this.zseg_match.close_parts.minutes;
- d.setHours(hours);
- d.setMinutes(minutes);
- iter = this.newIterator({
- begin: d,
- interval: 1,
- inZone: this.market_tz,
- outZone: this.market_tz
- });
- }
- return iter.next();
-};
-
-/**
- * Primitive to find the previous matching time segment taking into account rules for adjacent sessions.
- * If the date lands exactly on the open or close time for a session, then it will still seek to the previous market session.
- * @param {date} date A start date time in the market_tz timezone.
- * @param {boolean} open True if looking for an open time
- * @return {date} A date in the market_tz timezone that falls somewhere in a matching time segment. Probably 1 before close. Or null of no rules are defined.
- * @memberof CIQ.Market
- * @since 05-2016-10
- * @private
- */
-CIQ.Market.prototype._find_prev_segment = function (date, open) {
- if (!this.market_def) return null; // special case
- if (!this.rules) return null; //special case
- var d = new Date(+date);
- var iter = this.newIterator({
- begin: d,
- interval: 1,
- inZone: this.market_tz,
- outZone: this.market_tz
- });
-
- var wasOpenIntraDay = this._wasOpenIntraDay(d);
-
- // adjust edge cases to force a previous instance
- // if we are at the exact open or close time, go back one tick to force a previous session
- if (wasOpenIntraDay === null) {
- // move back one minute... not in the market clock.
- d = new Date(d - 60000);
- // then see if there was a session a minute ago... if so, then we were at the exact open or close time
- wasOpenIntraDay = this._wasOpenIntraDay(d);
- } else {
- if (
- (open &&
- d.getHours() === this.zseg_match.open_parts.hours &&
- d.getMinutes() === this.zseg_match.open_parts.minutes) ||
- (!open &&
- d.getHours() === this.zseg_match.close_parts.hours &&
- d.getMinutes() === this.zseg_match.close_parts.minutes)
- ) {
- d = iter.previous();
- }
- }
-
- if (wasOpenIntraDay) {
- var hours = this.zseg_match.open_parts.hours;
- var minutes = this.zseg_match.open_parts.minutes;
- d.setHours(hours);
- d.setMinutes(minutes);
- iter = this.newIterator({
- begin: d,
- interval: 1,
- inZone: this.market_tz,
- outZone: this.market_tz
- });
- d = iter.previous();
-
- if (this.zseg_match.close_parts.hours === hours) {
- if (this.zseg_match.close_parts.minutes === minutes) {
- // segments are adjacent use the previous
- if (open) {
- return iter.next();
- }
- return d;
- }
- }
- if (this.zseg_match.adjacent_child) {
- // segments are adjacent use the previous
- return d;
- }
- if (open) {
- // segments are not adjacent go back
- return iter.next();
- }
- return d;
- }
- return iter.previous();
-};
-
-/**
- * Toggle on/off a market session by name.
- *
- * - **Important:** Enabling/Disabling market sessions will not automatically filter-out data from the chart, but simply adjust the market iterators so the x-axis can be displayed accordingly in the absence of data for the excluded sessions.
- * - Data filtering can be done:
- * - Manually by requesting pertinent data from your feed and calling {@link CIQ.ChartEngine#loadChart}
- * - Automatically by using the {@link CIQ.ExtendedHours} visualization and filtering add-on.
- *
- * @param {string} session_name A session name matching a valid name present in the market definition.
- * @param {object} [inverted] Any true value (`true`, non-zero value or string) passed here will enable the session, otherwise the session will be disabled.
- * @memberof CIQ.Market
- * @since 06-2016-02
- */
-CIQ.Market.prototype.disableSession = function (session_name, inverted) {
- var inverted_ = false;
- if (typeof inverted !== "undefined" && inverted) {
- inverted_ = true;
- }
- if (session_name) {
- for (var i = 0; i < this.normalHours.length; i++) {
- if (this.normalHours[i].name === session_name) {
- this.normalHours[i].enabled = inverted_;
- }
- }
- for (i = 0; i < this.extraHours.length; i++) {
- if (this.extraHours[i].name === session_name) {
- this.extraHours[i].enabled = inverted_;
- }
- }
- }
-};
-
-/**
- * Enable a market session by name. See {@link CIQ.Market#disableSession} for full usage details.
- * @param {string} session_name A session name
- * @memberof CIQ.Market
- * @since 06-2016-02
- */
-CIQ.Market.prototype.enableSession = function (session_name) {
- this.disableSession(session_name, "enable_instead");
-};
-
-/**
- * Parses the market definition for a list of market names, and enables each one-by-one, see {@link CIQ.Market#enableSession} and {@link CIQ.Market#disableSession}.
- * - **Important:** Enabling/Disabling market sessions will not automatically filter-out data from the chart, but simply adjust the market iterators so the x-axis can be displayed accordingly in the absence of data for the excluded sessions.
- * @memberof CIQ.Market
- * @since 6.0.0
- */
-CIQ.Market.prototype.enableAllAvailableSessions = function () {
- var marketSessions = this.getSessionNames();
- for (var i = 0; i < marketSessions.length; i++) {
- this.enableSession(marketSessions[i].name);
- }
-};
-
-/**
- * Get the close date/time for the trading day or specific session.
- * @param {date} [date=now] date The date on which to check.
- * @param {string} [session_name] Specific market session. If `session_name` is not passed in, the first close time of the day will be returned,
- * depending on the sessions that are enabled. If a session name is passed in, then not only does the market session
- * need to be open on the day of `date`, but also within the time of the specified session. Otherwise, null will be returned.
- * Pass in "" to specify only the default session when other session are also active.
- * @param {string} [inZone] Optional datazone to translate from - If no market zone is present it will assume browser time.
- * @param {string} [outZone] Optional datazone to translate to - If no market zone is present it will assume browser time.
- * @return {date} Close date/time for the trading session or null if the market is
- * closed for the given date.
- * @memberof CIQ.Market
- * @since 05-2016-10
- */
-CIQ.Market.prototype.getClose = function (date, session_name, inZone, outZone) {
- if (!this.market_def) return null; // special case
- if (!this.rules) return null; //special case
- var d = date;
- if (!date) {
- d = new Date();
- inZone = null; // if they don't send the date we set one up in browser time, so need to remove the inZone
- }
- d = this._convertToMarketTZ(d, inZone);
-
- if (typeof session_name !== "undefined") {
- if (this._wasOpenIntraDay(d)) {
- if (this.zseg_match.name === session_name) {
- d.setHours(
- this.zseg_match.close_parts.hours,
- this.zseg_match.close_parts.minutes,
- 0,
- 0
- );
- d = this._convertFromMarketTZ(d, outZone);
- return d;
- }
- }
- } else {
- if (this._wasOpenDaily(d)) {
- var zseg_match = this.zseg_match;
-
- //find the last session of the day
- while (zseg_match.child_) {
- zseg_match = zseg_match.child_;
- }
-
- //find the last enabled session ... maybe back where we started
- while (!zseg_match.enabled) {
- zseg_match = zseg_match.parent_;
- }
-
- d.setHours(
- zseg_match.close_parts.hours,
- zseg_match.close_parts.minutes,
- 0,
- 0
- );
- d = this._convertFromMarketTZ(d, outZone);
- return d;
- }
- }
- return null;
-};
-
-/**
- * Get the close time for the current market session, or if the market is closed, the close time for the next market session.
- * @param {date} [date=now] date The date on which to check.
- * @param {string} [inZone] Optional datazone to translate from - If no market zone is present it will assume browser time.
- * @param {string} [outZone] Optional datazone to translate to - If no market zone is present it will assume browser time.
- * @return {date} A date set to the close time of the next open market session.
- * @memberof CIQ.Market
- * @since 05-2016-10
- */
-CIQ.Market.prototype.getNextClose = function (date, inZone, outZone) {
- if (!this.market_def) return null; // special case
- if (!this.rules) return null; //special case
-
- var d = date;
- if (!date) {
- d = new Date();
- inZone = null; // if they don't send the date we set one up in browser time, so need to remove the inZone
- }
- d = this._convertToMarketTZ(d, inZone);
- if (!this._wasOpenIntraDay(d)) {
- var iter = this.newIterator({
- begin: d,
- interval: 1,
- inZone: this.market_tz,
- outZone: this.market_tz
- });
- d = iter.next();
- }
- var date_ = d.getDate();
- var zseg_match = this.zseg_match;
- while (zseg_match.adjacent_child) {
- zseg_match = zseg_match.adjacent_child;
- date_ += 1;
- }
- d.setDate(date_);
- d.setHours(
- zseg_match.close_parts.hours,
- zseg_match.close_parts.minutes,
- 0,
- 0
- );
- d = this._convertFromMarketTZ(d, outZone);
- return d;
-};
-
-/**
- * Get the next market session open time. If the requested date is the opening time for the session, then
- * it will iterate to opening time for the next market session.
- * @param {date} [date=now] date An The date on which to check.
- * @param {string} [inZone] Optional datazone to translate from - If no market zone is present it will assume browser time.
- * @param {string} [outZone] Optional datazone to translate to - If no market zone is present it will assume browser time.
- * @return {date} A date aligned to the open time of the next open session. If no rules are defined, it will return null.
- * @memberof CIQ.Market
- * @since 05-2016-10
- */
-CIQ.Market.prototype.getNextOpen = function (date, inZone, outZone) {
- if (!this.market_def) return null; // special case
- if (!this.rules) return null; //special case
- var d = date;
- if (!date) {
- d = new Date();
- inZone = null; // if they don't send the date we set one up in browser time, so need to remove the inZone
- }
- d = this._convertToMarketTZ(d, inZone);
- d = this._find_next_segment(d);
- if (this.zseg_match.adjacent_parent) {
- d = this.getNextOpen(d, this.market_tz, this.market_tz);
- d = this._convertFromMarketTZ(d, outZone);
- return d;
- }
- d.setHours(this.zseg_match.open_parts.hours);
- d.setMinutes(this.zseg_match.open_parts.minutes);
- d = this._convertFromMarketTZ(d, outZone);
- return d;
-};
-
-/**
- * Get the open date/time for a market day or specific session.
- * @param {date} [date=now] date The date on which to check.
- * @param {string} [session_name] Specific market session. If `session_name` is not passed in, the first open time of the day will be returned,
- * depending on the sessions that are enabled. If a session name is passed in, then not only does the market session
- * need to be open on the day of `date`, but also within the time of the specified session. Otherwise, null will be returned. Pass in "" to
- * specify only the default session when other session are also active.
- * @param {string} [inZone] Optional datazone to translate from - If no market zone is present it will assume browser time.
- * @param {string} [outZone] Optional datazone to translate to - If no market zone is present it will assume browser time.
- * @return {date} A date time for the open of a session or null if the market is
- * closed for the given date or there are no market rules to check.
- * @memberof CIQ.Market
- * @since 05-2016-10
- */
-CIQ.Market.prototype.getOpen = function (date, session_name, inZone, outZone) {
- if (!this.market_def) return null; // special case
- if (!this.rules) return null; //special case
- var d = date;
- if (!date) {
- d = new Date();
- inZone = null; // if they don't send the date we set one up in browser time, so need to remove the inZone
- }
- d = this._convertToMarketTZ(d, inZone);
- if (typeof session_name !== "undefined") {
- if (this._wasOpenIntraDay(d)) {
- if (this.zseg_match.name == session_name) {
- d.setHours(
- this.zseg_match.open_parts.hours,
- this.zseg_match.open_parts.minutes,
- 0,
- 0
- );
- d = this._convertFromMarketTZ(d, outZone);
- return d;
- }
- }
- } else {
- if (this._wasOpenDaily(d)) {
- var zseg_match = this.zseg_match;
-
- //find all of the parents if any
- while (zseg_match.parent_) {
- zseg_match = zseg_match.parent_;
- }
-
- //find the first enabled child ... might end up back where we started
- while (!zseg_match.enabled) {
- zseg_match = zseg_match.child_;
- }
-
- d.setHours(
- zseg_match.open_parts.hours,
- zseg_match.open_parts.minutes,
- 0,
- 0
- );
- d = this._convertFromMarketTZ(d, outZone);
- return d;
- }
- }
- return null;
-};
-
-/**
- * Gets the normal open time for the current market; that is, the time the market typically opens.
- * In cases where there are two trading sessions, the first is used.
- *
- * @return {string} The normal open in HH:mm format.
- * @memberof CIQ.Market
- * @since 8.1.0
- */
-CIQ.Market.prototype.getNormalOpen = function () {
- const { market_def, rules } = this;
- if (!(market_def && rules)) return "00:00";
- if (market_def.normal_daily_open) return market_def.normal_daily_open;
- if (market_def.name === "FOREX") return "17:00";
- return rules.find(({ name }) => !name || name === "").open;
-};
-
-/**
- * Gets the normal close time for the current market; that is, the time the market typically
- * closes. In cases where there are two trading sessions, the second is used.
- *
- * @return {string} The normal close in HH:mm format.
- * @memberof CIQ.Market
- * @since 8.1.0
- */
-CIQ.Market.prototype.getNormalClose = function () {
- const { market_def, rules } = this;
- if (!(market_def && rules)) return "24:00";
- if (market_def.normal_daily_close) return market_def.normal_daily_close;
- if (market_def.name === "FOREX") return "17:00";
- return rules
- .filter(({ dayofweek, name }) => dayofweek && (!name || name === ""))
- .pop().close;
-};
-
-/**
- * Get the previous session close time.
- * If the date lands exactly on the close time for a session then it will still seek to the previous market session's close.
- * @param {date} [date=now] date The date on which to check.
- * @param {string} [inZone] Optional datazone to translate from - If no market zone is present it will assume browser time.
- * @param {string} [outZone] Optional datazone to translate to - If no market zone is present it will assume browser time.
- * @return {date} A date aligned to the previous close date/time of a session. If no rules are defined, it will return null.
- * @memberof CIQ.Market
- * @since 05-2016-10
- */
-CIQ.Market.prototype.getPreviousClose = function (date, inZone, outZone) {
- if (!this.market_def) return null; // special case
- if (!this.rules) return null; //special case
- var d = date;
- if (!date) {
- d = new Date();
- inZone = null; // if they don't send the date we set one up in browser time, so need to remove the inZone
- }
- d = this._convertToMarketTZ(d, inZone);
- d = this._find_prev_segment(d, false);
- if (this.zseg_match.adjacent_child) {
- return this.getPreviousClose(d, this.market_tz, this.market_tz);
- }
- d.setHours(this.zseg_match.close_parts.hours);
- d.setMinutes(this.zseg_match.close_parts.minutes);
- d = this._convertFromMarketTZ(d, outZone);
- return d;
-};
-
-/**
- * Get the previous session open time. If the date lands exactly on the open time for a session then
- * it will still seek to the previous market session's open.
- * @param {date} [date=now] date An The date on which to check.
- * @param {string} [inZone] Optional datazone to translate from - If no market zone is present it will assume browser time.
- * @param {string} [outZone] Optional datazone to translate to - If no market zone is present it will assume browser time.
- * @return {date} A date aligned to previous open date/time of a session. If no rules are defined, it will return null.
- * @memberof CIQ.Market
- * @since 05-2016-10
- */
-CIQ.Market.prototype.getPreviousOpen = function (date, inZone, outZone) {
- if (!this.market_def) return null; // special case
- if (!this.rules) return null; //special case
- var d = date;
- if (!date) {
- d = new Date();
- inZone = null; // if they don't send the date we set one up in browser time, so need to remove the inZone
- }
- d = this._convertToMarketTZ(d, inZone);
- d = this._find_prev_segment(d, true);
- if (this.zseg_match.adjacent_parent) {
- return this.getPreviousOpen(d, this.market_tz, this.market_tz);
- }
- d.setHours(this.zseg_match.open_parts.hours);
- d.setMinutes(this.zseg_match.open_parts.minutes);
- d = this._convertFromMarketTZ(d, outZone);
- return d;
-};
-
-/**
- * Return the session name for a date. If the name is defined and if the date
- * lands in a session that is open. Otherwise return null.
- * @param {date} date A date object
- * @param {string} [inZone] Timezone of incoming date - If no market zone is present it will assume browser time.
- * @return {object} String or null
- * @memberOf CIQ.Market
- */
-CIQ.Market.prototype.getSession = function (date, inZone) {
- date = this._convertToMarketTZ(date, inZone);
- if (this._wasOpenIntraDay(date) && this.zseg_match) {
- return this.zseg_match.name;
- }
- return null;
-};
-
-/**
- * @return {date} Current time in the market zone
- * @memberof CIQ.Market
- * @since 04-2016-08
- */
-CIQ.Market.prototype.marketZoneNow = function () {
- return this._convertToMarketTZ(new Date());
-};
-
-/**
- * @return {boolean} `true` if this market is hour aligned.
- * @memberof CIQ.Market
- * @since 04-2016-08
- */
-CIQ.Market.prototype.isHourAligned = function () {
- return this.hour_aligned;
-};
-
-/**
- * Checks if the market is currently open.
- * @return {object} An object with the open market session's details, if the market is open right now. Or `null` if no sessions are currently open.
- * @memberof CIQ.Market
- * @since 04-2016-08
- */
-CIQ.Market.prototype.isOpen = function () {
- var now = new Date();
- if (this.market_tz) {
- now = new this.tz_lib(now.getTime(), this.market_tz);
- }
- return this._wasOpenIntraDay(now);
-};
-
-/**
- * Checks if today it is a market day.
- * @return {object} An object with the open market session's details, if it is a market day. Or `null` if it is not a market day.
- * @memberof CIQ.Market
- * @since 04-2016-08
- */
-CIQ.Market.prototype.isMarketDay = function () {
- var now = new Date();
- if (this.market_tz) {
- now = new this.tz_lib(now.getTime(), this.market_tz);
- }
- return this._wasOpenDaily(now);
-};
-
-/**
- * Checks if a supplied date is a market day. Only the date is examined; hours, minutes, seconds are ignored
- * @param {date} date A date
- * @return {object} An object with the open market session's details, if it is a market day. Or `null` if it is not a market day.
- * @memberof CIQ.Market
- * @since 04-2016-08
- */
-CIQ.Market.prototype.isMarketDate = function (date) {
- return this._wasOpenDaily(date);
-};
-
-/**
- * Creates iterators for the associated Market to traverse through time taking into account market hours.
- * An iterator instance can go forward or backward in time any arbitrary amount.
- * However, the internal state cannot be changed once it is constructed. A new iterator should be
- * constructed whenever one of the parameters changes. For example, if the
- * `interval` changes a new iterator will need to be built. If the `displayZone`
- * or `dataZone` changes on the market, new iterators will also need to be
- * constructed.
- *
- * See {@link CIQ.Market.Iterator} for all available methods.
- *
- * See the following convenience functions: {@link CIQ.ChartEngine#getNextInterval} and {@link CIQ.ChartEngine#standardMarketIterator}
- *
- * @param {object} parms Parameters used to initialize the Market object.
- * @param {string} [parms.interval] A valid interval as required by {@link CIQ.ChartEngine#setPeriodicity}. Default is 1 (minute).
- * @param {number} [parms.periodicity] A valid periodicity as required by {@link CIQ.ChartEngine#setPeriodicity}. Default is 1.
- * @param {string} [parms.timeUnit] A valid timeUnit as required by {@link CIQ.ChartEngine#setPeriodicity}. Default is "minute"
- * @param {date} [parms.begin] The date to set as the start date for this iterator instance. Default is `now`. Will be assumed to be `inZone` if one set.
- * @param {string} [parms.inZone] A valid timezone from the timeZoneData.js library. This should represent the time zone for any input dates such as `parms.begin` in this function or `parms.end` in {@link CIQ.Market.Iterator#futureTick}. Defaults to browser timezone if none set. - If no market zone is present it will assume browser time.
- * @param {string} [parms.outZone] A valid timezone from the timeZoneData.js library. This should represent the time zone for the returned dates. Defaults to browser timezone if none set. - If no market zone is present it will assume browser time.
- * @return {object} A new iterator.
- * @memberof CIQ.Market
- * @since 04-2016-08
- * @example
- var iter = stxx.chart.market.newIterator(
- {
- 'begin': now,
- 'interval': stxx.layout.interval,
- 'periodicity': stxx.layout.periodicity,
- 'timeUnit': stxx.layout.timeUnit,
- 'inZone': stxx.dataZone,
- 'outZone': stxx.displayZone
- }
- );
- */
-CIQ.Market.prototype.newIterator = function (parms) {
- var _multiple = false;
- if (parms.periodicity) {
- _multiple = parms.periodicity;
- } else if (parms.multiple) {
- _multiple = parms.multiple;
- }
- var _interval = parms.interval;
- if (!_interval) {
- _interval = "minute";
- }
- if (_interval == "hour") _interval = 60;
- if (!_multiple) {
- _multiple = 1;
- }
- if (!parms.begin) {
- parms.begin = new Date();
- parms.inZone = null;
- }
- if (_interval == parseInt(_interval, 10)) {
- _interval = parseInt(_interval, 10); // in case it was a string, which is allowed in setPeriodicity.
-
- // if the periodicity<1 then the x-axis might be in seconds (<1/60, msec)
- if (parms.periodicity < 1 / 60) {
- _multiple = _multiple * _interval * 60000;
- _interval = "millisecond";
- } else if (parms.periodicity < 1) {
- _multiple = _multiple * _interval * 60;
- _interval = "second";
- } else {
- _multiple = _multiple * _interval;
- _interval = "minute";
- }
- }
- if (parms.timeUnit) {
- if (parms.timeUnit === "millisecond") {
- _interval = parms.timeUnit;
- } else if (parms.timeUnit === "second") {
- _interval = parms.timeUnit;
- } else if (parms.timeUnit === "tick") {
- _interval = "second";
- }
- }
- if (_interval == "tick") _interval = "second";
- parms.interval = _interval;
- parms.multiple = _multiple;
- parms.market = this;
- return new CIQ.Market.Iterator(parms);
-};
-
-/**
- * Calculate whether this market was open on some date. This will depend on
- * the data used when creating this market. This function does not take into
- * account intraday data. It simply checks the date to see if the market was
- * open at all on that day. Hours, minutes, seconds are ignored.
- * @param {date} historical_date Javascript date object with timezone in the market time zone.
- * @return {boolean} true if the market was open.
- * @memberof CIQ.Market
- * @since 04-2016-08
- * @private
- */
-CIQ.Market.prototype._wasOpenDaily = function (historical_date) {
- return this._was_open(historical_date, false);
-};
-
-/**
- * Calculate whether this market was open on some date. This will depend on
- * The data used when creating this market. This function will take into
- * account intraday date that is minutes and seconds. Not only does a market
- * need to be open on the day in question but also within the time specified.
- * @param {date} historical_date Javascript date object with timezone in the market time zone.
- * @return {boolean} true if the market was open.
- * @memberof CIQ.Market
- * @since 04-2016-08
- * @private
- */
-CIQ.Market.prototype._wasOpenIntraDay = function (historical_date) {
- return this._was_open(historical_date, true);
-};
-
-/**
- * Given some javascript date object calculate whether this market was open.
- * Use _wasOpenDaily or _wasOpenIntraDay instead. As a special case if
- * no market json has been defined this function will always return true.
- * @param {date} historical a valid Javascript date object with timezone in the market time zone.
- * @param {boolean} intra_day true if intraday (will check between open and close times)
- * @return {object} matching segment if any, or null if not
- * @private
- */
-CIQ.Market.prototype._was_open = function (historical, intra_day) {
- // This function will reset all of the `z` properties to match the market segment matching `historical`
- // Whether the matching segment has changed helps to determine whether we should reset the date to the
- // beginning of the market segment. Here we store a record of the previously set `zseg_match` to
- // facilitate that determination later.
- var previously_set_zseg_match = this.zseg_match;
-
- this.zopen_hour = 0;
- this.zopen_minute = 0;
- this.zclose_hour = 0;
- this.zclose_minute = 0;
- this.zmatch_open = false;
- this.zseg_match = null;
- if (!this.market_def || !this.rules) {
- // special case, 24h security
- this.zclose_hour = 24;
- return true;
- }
- var normally_open = false;
- var extra_open = false;
- var year = historical.getFullYear();
- var month = historical.getMonth() + 1;
- var day = historical.getDay();
- var date = historical.getDate();
- var hour = historical.getHours();
- var minutes = historical.getMinutes();
- var seconds = historical.getSeconds();
- var segment;
- var midnight_secs = hour * 60 * 60 + minutes * 60 + seconds;
-
- if (typeof intra_day === "undefined") {
- intra_day = true;
- }
-
- var i;
- for (i = 0; i < this.normalHours.length; i++) {
- segment = this.normalHours[i];
- if (!segment.enabled) {
- continue;
- }
- normally_open = segment.dayofweek === day;
- if (normally_open && intra_day) {
- normally_open =
- midnight_secs >= segment.open && midnight_secs < segment.close;
- }
- if (normally_open) {
- if (!intra_day && this.zseg_match) {
- if (
- segment.open_parts.hours > this.zopen_hour ||
- (segment.open_parts.hours == this.zopen_hour &&
- segment.open_parts.minutes > this.zopen_minute)
- ) {
- continue;
- }
- }
-
- // We may want to reset the date to the beginning of the segment if the `zseg_match` has
- // changed and if the segment is not one part of a single session (a trading period extending
- // into a second day). We determine these factors by comparing `segment` to the previously set
- // `zseg_match` and by checking whether the `segment` has an `adjacent_parent` or `adjacent_child`.
- // If these factors indicate we should reset reset the time to the beginning of the segment, we
- // store that determination on the object to know to take that action later.
- if (
- segment !== previously_set_zseg_match &&
- !segment.adjacent_parent &&
- !segment.adjacent_child
- ) {
- this.shouldResetToBeginningOfSegment = true;
- }
-
- this.zopen_hour = segment.open_parts.hours;
- this.zopen_minute = segment.open_parts.minutes;
- this.zclose_hour = segment.close_parts.hours;
- this.zclose_minute = segment.close_parts.minutes;
- this.zmatch_open = midnight_secs === segment.open;
- this.zseg_match = segment;
- if (intra_day) break;
- }
- }
-
- for (i = 0; i < this.extraHours.length; i++) {
- segment = this.extraHours[i];
- if (!segment.enabled) {
- continue;
- }
- if ("*" === segment.year || year === segment.year) {
- if (month === segment.month && date === segment.day) {
- extra_open =
- (!intra_day && segment.open) ||
- (midnight_secs >= segment.open && midnight_secs < segment.close);
- if (!extra_open && this.zseg_match) {
- normally_open = false;
- this.zopen_hour = 0;
- this.zopen_minute = 0;
- this.zclose_hour = 0;
- this.zclose_minute = 0;
- this.zmatch_open = false;
- this.zseg_match = null;
- }
- if (extra_open) {
- if (!intra_day && this.zseg_match) {
- if (
- segment.open_parts.hours > this.zopen_hour ||
- (segment.open_parts.hours == this.zopen_hour &&
- segment.open_parts.minutes > this.zopen_minute)
- ) {
- continue;
- }
- }
- this.zopen_hour = segment.open_parts.hours;
- this.zopen_minute = segment.open_parts.minutes;
- this.zclose_hour = segment.close_parts.hours;
- this.zclose_minute = segment.close_parts.minutes;
- this.zmatch_open = midnight_secs === segment.open;
- this.zseg_match = segment;
- if (intra_day) break;
- }
- }
- }
- }
-
- return this.zseg_match;
-};
-
-/**
- * Convenience function for unit testing.
- * @param {date} testDate A date
- * @return {boolean} True if the market was closed on the given date
- * @memberOf CIQ.Market
- */
-CIQ.Market.prototype._wasClosed = function (testDate) {
- return !this._was_open(testDate, true);
-};
-
-/**
- * Convenience function for unit testing.
- * @param {date} testDate A date
- * @return {boolean} True if the market was open on the given date
- * @memberOf CIQ.Market
- */
-CIQ.Market.prototype._wasOpen = function (testDate) {
- return this._was_open(testDate, true);
-};
-
-/**
- * Get the difference in milliseconds between two time zones. May be positive or
- * negative depending on the time zones. The purpose is to shift the source
- * time zone some number of milliseconds to the target timezone. For example shifting
- * a data feed from UTC to Eastern time. Or shifting Eastern time to Mountain
- * time for display purposes. Note that it is important to pass the source
- * and the target in the correct order. The algorithm does source - target. This
- * will calculate the correct offset positive or negative.
- * @param {date} date A date object. Could be any date object the javascript one
- * or for example the timezone.js one. Must implement getTime() and
- * getTimezoneOffset()
- * @param {string} src_tz_str The source time zone. For example the data feed
- * @param {string} target_tz_str The target time zone for example the market.
- * @return {number} The number of milliseconds difference between the time
- * zones.
- * @memberOf CIQ.Market
- */
-CIQ.Market.prototype._tzDifferenceMillis = function (
- date,
- src_tz_str,
- target_tz_str
-) {
- var millis = 0;
- var src_date = date;
- var target_date = date;
- var minutes = src_date.getTimezoneOffset() - target_date.getTimezoneOffset();
- millis = minutes * 60 * 1000;
- return millis;
-};
-
-/**
- * Static function that reads the json rules in the market definition and
- * creates in memory time segments that are used later to match market dates.
- * @param {object} market An instance of a market.
- * @memberOf CIQ.Market
- */
-CIQ.Market._createTimeSegments = function (market) {
- var link_adjacent = function (r0_, r1_) {
- if (r0_.close_parts.hours === 24 && r1_.open_parts.hours === 0) {
- if (r1_.open_parts.minutes === 0) {
- if (p_rule.dayofweek === rd.dayofweek - 1) {
- return true;
- }
- if (p_rule.dayofweek === 6 && rd.dayofweek === 0) {
- return true;
- }
- }
- }
- return false;
- };
- var p_rule;
- for (var i = 0; i < market.rules.length; i++) {
- var rule = JSON.parse(JSON.stringify(market.rules[i]));
- if (typeof rule.open === "undefined" && typeof rule.close === "undefined") {
- rule.open = "00:00";
- rule.close = "00:00";
- }
- if (!rule.hasOwnProperty("name")) {
- rule.name = "";
- }
- try {
- var rd;
- if (typeof rule.dayofweek !== "undefined") {
- rule.year = "*";
- rd = _TimeSegmentS._createDayOfWeekSegment(market, rule);
- if (p_rule) {
- if (p_rule.dayofweek === rd.dayofweek) {
- //These links are used for finding open and close times
- //On the same day in multiple sessions
- p_rule.child_ = rd;
- rd.parent_ = p_rule;
- } else {
- if (link_adjacent(p_rule, rd)) {
- //These links are used for finding open and close
- //times for sessions that span days
- p_rule.adjacent_child = rd;
- rd.adjacent_parent = p_rule;
- }
- }
- }
- p_rule = rd;
- } else if (typeof rule.date !== "undefined") {
- rule.isDayOfWeek = false;
- rule.dayofweek = -1;
- rd = _TimeSegmentS._createDateTimeSegment(market, rule);
- } else {
- console.log("Error, unknown rule type " + rule);
- }
- if (market.enabled_by_default) {
- for (var x = 0; x < market.enabled_by_default.length; x++) {
- var n = market.enabled_by_default[x];
- if (rd.name === n) {
- rd.enabled = true;
- break;
- }
- }
- } else {
- //always enabled if no defaults are defined
- //rd.enabled = true;
- }
- } catch (err) {
- console.log("Error, creating market rules " + err);
- }
- }
-};
-
-/**
- * Internal static utility methods used to create market time segments.
- * @private
- */
-CIQ.Market._timeSegment = {};
-var _TimeSegmentS = CIQ.Market._timeSegment;
-
-_TimeSegmentS.re_wild_card_iso = /^(\*)-(\d\d)-(\d\d)$/;
-_TimeSegmentS.re_regular_iso = /^(\d\d\d\d)-(\d\d)-(\d\d)$/;
-_TimeSegmentS.re_split_hours_minutes = /^(\d\d):(\d\d)$/;
-_TimeSegmentS.re_split_hour_minutes = /^(\d):(\d\d)$/;
-
-/**
- * Create a hash code for a string. We may move this to 3rd party later if
- * we find a wider need for it. This came from StackOverflow and claims to be
- * the same implementation used by Java.
- * @param {string} str A string.
- * @return {number} A number suitable for
- * @private
- */
-_TimeSegmentS._hashCode = function (str) {
- var hash = 0,
- i,
- chr,
- len;
- if (str.length === 0) return hash;
- for (i = 0, len = str.length; i < len; i++) {
- chr = str.charCodeAt(i);
- hash = (hash << 5) - hash + chr;
- hash |= 0; // Convert to 32bit integer
- }
- return hash;
-};
-
-/**
- * Split the hours and minutes from a json time segment rule.
- * @param {string} str \d\d:\d\d or \d:\d\d
- * @return {object} {minutes:int, hours:int}
- * @private
- */
-_TimeSegmentS._splitHoursMinutes = function (str) {
- var parts = _TimeSegmentS.re_split_hour_minutes.exec(str);
- var ret_val = { hours: NaN, minutes: NaN };
- if (parts === null) {
- parts = _TimeSegmentS.re_split_hours_minutes.exec(str);
- if (parts === null) {
- return ret_val;
- }
- }
- ret_val.hours = parseInt(parts[1], 10);
- ret_val.minutes = parseInt(parts[2], 10);
- return ret_val;
-};
-
-/**
- * Create a time segment for some day of the week. This creates a wildcard
- * segment that matches the same weekday in any month and any year.
- * @param {object} market The instance of this market
- * @param {object} rule Represents the data from one rule in the JSON
- * @return {object}
- * configuration.
- * @private
- */
-_TimeSegmentS._createDayOfWeekSegment = function (market, rule) {
- var data = {
- name: rule.name,
- isDayOfWeek: true,
- dayofweek: rule.dayofweek,
- date_str: "*",
- open_parts: _TimeSegmentS._splitHoursMinutes(rule.open),
- close_parts: _TimeSegmentS._splitHoursMinutes(rule.close),
- open: _TimeSegmentS._secSinceMidnight(market, rule.open, true),
- close: _TimeSegmentS._secSinceMidnight(market, rule.close, false),
- child_: false,
- parent_: false,
- adjacent_child: false,
- adjacent_parent: false,
- enabled: false
- };
- if (data.name === "") {
- data.enabled = true;
- }
- data.hash_code = this._hashCode((data.open + data.close).toString());
- market.normalHours.push(data);
- return data;
-};
-
-/**
- * Create a time segment for a specific date and time. This can also create
- * a wild card segment that matches any year with a specific day and specific
- * month. For example *-12-25 to match all Christmas days. It can also build
- * any specific year month day open close time that will only match that
- * specific range.
- * @param {object} market an instance of a market
- * @param {object} rule a single rule from a market definition
- * @return {object|undefined} Undefined if this function works on the market object.
- * @private
- */
-_TimeSegmentS._createDateTimeSegment = function (market, rule) {
- var pieces = this.re_regular_iso.exec(rule.date);
- var year;
- if (pieces === null) {
- pieces = this.re_wild_card_iso.exec(rule.date);
- if (pieces === null) {
- console.log("Warning: invalid date format on rule -> " + rule.date);
- return;
- }
- year = "*"; //all years
- } else {
- year = parseInt(pieces[1], 10);
- }
- var data = {
- name: rule.name,
- isDayOfWeek: false,
- dayofweek: -1,
- year: year,
- month: parseInt(pieces[2], 10),
- day: parseInt(pieces[3], 10),
- date_str: rule.date,
- open_parts: _TimeSegmentS._splitHoursMinutes(rule.open),
- close_parts: _TimeSegmentS._splitHoursMinutes(rule.close),
- open: _TimeSegmentS._secSinceMidnight(market, rule.open, true),
- close: _TimeSegmentS._secSinceMidnight(market, rule.close, false),
- enabled: false
- };
- if (data.name === "") {
- data.enabled = true;
- }
- data.hash_key = this._hashCode(data.date_str + data.open + data.close);
- market.extraHours.push(data);
- return data;
-};
-
-/**
- * Calculate the seconds since midnight for some time string. These time strings
- * come from the market definition. These are intended to be open and close
- * times.
- * @param {object} market An instance of a market
- * @param {string} time_str A time string like this "\d\d:\d\d"
- * @param {boolean} open_time If true the time is used for opening a market
- * @return {number} Seconds since midnight
- * otherwise the time is used for closing a market. This is so that we can
- * handle 00:00 and 24:00.
- * @private
- */
-_TimeSegmentS._secSinceMidnight = function (market, time_str, open_time) {
- var parts = time_str.split(":");
- var hours = parseInt(parts[0], 10);
- var minutes = parseInt(parts[1], 10);
- var seconds = hours * 60 * 60 + minutes * 60;
-
- if (!open_time) {
- if (hours === 24) {
- seconds = hours * 60 * 60 + 1;
- }
- }
- return seconds;
-};
-
-/**
- * Converts from the given timezone into the market's native time zone
- * If no market zone is present, the date will be returned unchanged.
- * @param {date} dt JavaScript Date
- * @param {string} [tz] timezoneJS timezone, or null to indicate browser localtime/UTC (dataZone)
- * @return {date} A JavaScript Date offset by the timezone change
- * @memberOf CIQ.Market
- */
-CIQ.Market.prototype._convertToMarketTZ = function (dt, tz) {
- //if(!this.market_tz) return dt;
- var tzdt;
- if (tz) {
- tzdt = new this.tz_lib(
- dt.getFullYear(),
- dt.getMonth(),
- dt.getDate(),
- dt.getHours(),
- dt.getMinutes(),
- dt.getSeconds(),
- dt.getMilliseconds(),
- tz
- );
- } else {
- tzdt = new this.tz_lib(
- dt.getFullYear(),
- dt.getMonth(),
- dt.getDate(),
- dt.getHours(),
- dt.getMinutes(),
- dt.getSeconds(),
- dt.getMilliseconds()
- );
- }
- if (tzdt.setTimezone) tzdt.setTimezone(this.market_tz);
- return new Date(
- tzdt.getFullYear(),
- tzdt.getMonth(),
- tzdt.getDate(),
- tzdt.getHours(),
- tzdt.getMinutes(),
- tzdt.getSeconds(),
- tzdt.getMilliseconds()
- );
-};
-
-/**
- * Converts to the given timezone from the market's native time zone.
- * If no market zone is present, the date will be returned un changed.
- * @param {date} dt JavaScript Date
- * @param {string} [tz] timezoneJS timezone, or null to indicate browser localtime/UTC (displayZone)
- * @return {date} A JavaScript Date offset by the timezone change
- * @memberOf CIQ.Market
- */
-CIQ.Market.prototype._convertFromMarketTZ = function (dt, tz) {
- //if(!this.market_tz) return dt;
- var tzdt = new this.tz_lib(
- dt.getFullYear(),
- dt.getMonth(),
- dt.getDate(),
- dt.getHours(),
- dt.getMinutes(),
- dt.getSeconds(),
- dt.getMilliseconds(),
- this.market_tz
- );
- if (tz) {
- if (tzdt.setTimezone) tzdt.setTimezone(tz);
- } else {
- return new Date(tzdt.getTime());
- }
- return new Date(
- tzdt.getFullYear(),
- tzdt.getMonth(),
- tzdt.getDate(),
- tzdt.getHours(),
- tzdt.getMinutes(),
- tzdt.getSeconds(),
- tzdt.getMilliseconds()
- );
-};
-
-/**
- * Builds an iterator instance and returns it to the requesting market when {@link CIQ.Market#newIterator} is called. Do not call this constructor directly.
- *
- * @name CIQ.Market.Iterator
- * @param {object} parms
- * @param {object} parms.begin A dataset element from {@link CIQ.Chart.dataSet}
- * @param {CIQ.Market} parms.market An instane of {@link CIQ.Market}
- * @param {object} parms.periodicity A valid periodicity as require by {@link CIQ.ChartEngine#setPeriodicity}
- * @param {string} parms.interval Time interval: millisecond, second, minute, hour, day, week, or month.
- * @param {object} parms.multiple How many jumps to make on each interval loop.
- * @param {string} parms.inZone Datazone to translate from
- * @param {string} parms.outZone Datazone to translate to
- * @constructor
- * @since 04-2016-08
- * @example
- var market24=new CIQ.Market();
- var iter_parms = {
- 'begin': stxx.chart.dataSet[stxx.chart.dataSet.length-1].DT, // last item on the dataset
- 'interval': stxx.layout.interval,
- 'periodicity': stxx.layout.periodicity,
- 'timeUnit': stxx.layout.timeUnit,
- 'inZone': stxx.dataZone,
- 'outZone': stxx.dataZone
- };
- var iter = market24.newIterator(iter_parms);
- var next = iter.next();
- *
- */
-CIQ.Market.Iterator = function (parms) {
- this.market = parms.market;
- this.begin = parms.begin;
- this.interval = parms.interval;
- this.multiple = parms.multiple;
- this.inZone = parms.inZone;
- this.outZone = parms.outZone;
- this.clock = new CIQ.Market.Iterator._Clock(
- parms.market,
- parms.interval,
- parms.multiple
- );
- this.intraDay = this.clock.intra_day;
- if (this.intraDay)
- this.begin = this.market._convertToMarketTZ(this.begin, parms.inZone);
- this.clock._setStart(this.begin);
- this.clock.minutes_aligned = false;
-};
-
-/**
- * Returns the current date of the iterator without moving forwards or backwards.
- * Takes into account display zone settings.
- * @return {date} The current date of the iterator.
- * @memberof CIQ.Market.Iterator
- * @since 04-2016-08
- * @example
- * iteratorDate = iter.date();
- */
-CIQ.Market.Iterator.prototype.date = function () {
- return this.clock._date();
-};
-
-/**
- * Calculate the number of ticks from begin date to end date taking into account
- * market open, close, and holidays.
- * If the end date is older than the begin date,it will work backward into the past.
- * If the end date is newer than the begin date,it will work forward into the future.
- * Note that the begin date is set when this
- * instance of the iterator is created and one should NOT call `previous` or `next`
- * before calling this function, or the 'begin' pointer will change.
- * @param {object} parms An object containing the following properties:
- * @param {date} parms.end An end date. Will be assumed to be `inZone` if one set.
- * @param {number} [parms.sample_size] Default is 25. Maximum amount of time
- * (in milliseconds) taken to count ticks. If sample size is
- * reached before the number of ticks is found the number of ticks will be
- * estimated mathematically. The bigger the sample size couple with the
- * distance between begin date and end date affect how precise the return value
- * is.
- * @param {number} [parms.sample_rate] Default is 1000. Maximum number of ticks to evaluate before checking `parms.sample_size`.
- * @return {number} The number of ticks between begin and end.
- * @memberof CIQ.Market.Iterator
- * @since 04-2016-08
- * @example
- * // find out how many ticks in the past a date is from the beginning of the dataSet
- * // (assumes the target date is older than the first dataSet item)
- * var iter = this.standardMarketIterator(chart.dataSet[0].DT);
- * var ticks=iter.futureTick({someRandomDate});
- */
-CIQ.Market.Iterator.prototype.futureTick = function (parms) {
- this.clock.skip = 1;
- var ticks = 0;
- var end;
- if (this.intraDay)
- end = this.market._convertToMarketTZ(parms.end, this.inZone).getTime();
- else end = parms.end.getTime();
- var begin = this.clock.ctime;
- if (end === begin) {
- return ticks;
- }
- var sample_size = 2; //milliseconds // May not be necessary at all. Looks accurate whenever past 1,000 ticks into future
- var sample_rate = 1000; //iterations
- var sample_ctr = 0;
- if (parms.sample_size) {
- sample_size = parms.sample_size;
- }
- var start = new Date().getTime();
- var now;
- var ave;
- if (end > begin) {
- this.clock.forward = true;
- while (this.clock.ctime < end) {
- ticks += 1;
- sample_ctr += 1;
- this.clock._findNext();
- if (sample_ctr === sample_rate) {
- sample_ctr = 0;
- now = new Date().getTime();
- if (now - start >= sample_size) {
- ave = (this.clock.ctime - begin) / ticks;
- ticks = Math.floor((end - begin) / ave);
- break;
- }
- }
- }
- if (this.clock.ctime > end) {
- // if not an exact match, we are one tick too far in the future by now.
- // Go back one to return the tick that contains this time in its range. Rather than the next tick.
- ticks--;
- }
- } else {
- this.clock.forward = false;
- while (this.clock.ctime > end) {
- ticks += 1;
- sample_ctr += 1;
- this.clock._findPrevious();
- if (sample_ctr === sample_rate) {
- sample_ctr = 0;
- now = new Date().getTime();
- if (now - start >= sample_size) {
- ave = (begin - this.clock.ctime) / ticks;
- ticks = Math.floor((begin - end) / ave);
- break;
- }
- }
- }
- }
- return ticks;
-};
-
-/**
- * Checks if market is aligned and if iterator is intraday (daily intervals always align)
- * @return {boolean} true if this market is hour aligned.
- * @memberof CIQ.Market.Iterator
- * @since 04-2016-08
- */
-CIQ.Market.Iterator.prototype.isHourAligned = function () {
- return !this.intraDay || this.market.isHourAligned();
-};
-
-/**
- * Check and see if this Market is open now.
- * @return {object} An object with the open market session's details, if the market is open right now. Or `null` if no sessions are currently open.
- * @memberof CIQ.Market.Iterator
- * @since 04-2016-08
- */
-CIQ.Market.Iterator.prototype.isOpen = function () {
- return this.market.isOpen();
-};
-
-/**
- * Move the iterator one interval forward
- * @param {number} [skip] Default 1. Jump forward skip * periodicity at once.
- * @return {date} Next date in iterator `outZone`.
- * @alias next
- * @memberof CIQ.Market.Iterator
- * @since 04-2016-08
- * @example
- * now = iter.next();
- */
-CIQ.Market.Iterator.prototype.next = function (skip) {
- this.clock.skip = 1;
- if (skip) {
- this.clock.skip = skip;
- }
- this.clock.forward = true;
- for (var i = 0; i < this.clock.skip; i++) this.begin = this.clock._findNext();
- if (this.intraDay || this.market.convertOnDaily) {
- return this.market._convertFromMarketTZ(
- this.clock.display_date,
- this.outZone
- );
- }
- return this.clock.display_date;
-};
-
-/**
- * Does not move the iterator. Takes into account display zone settings.
- * Note. This is a convenience function for debugging or whatever else, but
- * should not be called in the draw loop in production.
- * @return {string} The current date of the iterator as a string.
- * @memberof CIQ.Market.Iterator
- * @since 04-2016-08
- * @private
- */
-CIQ.Market.Iterator.prototype.peek = function () {
- return this.clock._peek();
-};
-
-/**
- * Move the iterator one interval backward
- * @param {number} skip Default is one. Move this many multiples of interval.
- * @return {date} Previous date in iterator `outZone`.
- * @alias previous
- * @memberof CIQ.Market.Iterator
- * @since 04-2016-08
- * @example
- * now = iter.previous();
- */
-CIQ.Market.Iterator.prototype.previous = function (skip) {
- this.clock.skip = 1;
- if (skip) {
- this.clock.skip = skip;
- }
- this.clock.forward = false;
- for (var i = 0; i < this.clock.skip; i++)
- this.begin = this.clock._findPrevious();
- if (this.intraDay || this.market.convertOnDaily) {
- return this.market._convertFromMarketTZ(
- this.clock.display_date,
- this.outZone
- );
- }
- return this.clock.display_date;
-};
-
-/**
- * Internal object that simulates a clock that ticks forward and backwards
- * at different intervals. Used internally by the iterator and not intended
- * to be used outside of the context of a Market.
- * @param {object} market An instance of market.
- * @param {string} interval millisecond, second, minute, hour, day, week or month
- * @param {number} multiple Move in multiple of intervals.
- * @private
- */
-CIQ.Market.Iterator._Clock = function (market, interval, multiple) {
- // rationalize rolled up intervals for better performance
- if (multiple % 60 === 0 && interval === "second") {
- interval = "minute";
- multiple = multiple / 60;
- }
-
- this.market = market;
- this.interval = interval;
- this.multiple = multiple;
- this.intra_day = false;
- this.intervals = [];
- this.max_iters = 10080; //max minutes to check for rules. (one week);
-
- var tick_time = DAY_MILLIS,
- findNext = this._dayImpl;
- if (interval === "millisecond") {
- findNext = this._millisImpl;
- tick_time = 1;
- } else if (interval === "second") {
- findNext = this._secondImpl;
- tick_time = 1000;
- } else if (interval === "minute") {
- findNext = this._minuteImpl;
- tick_time = 60000;
- } else if (interval === "hour") {
- findNext = this._hourImpl;
- tick_time = HOUR_MILLIS;
- } else if (interval === "day") {
- findNext = this._dayImpl;
- tick_time = DAY_MILLIS;
- } else if (interval === "week") {
- findNext = this._weekImpl;
- tick_time = DAY_MILLIS * 7;
- } else if (interval === "month") {
- findNext = this._monthImpl;
- tick_time = DAY_MILLIS * 30;
- } else {
- console.log(
- 'Periodicity ERROR: "' +
- interval +
- '" is not a valid interval. Please see setPeriodicity() for details.'
- );
- }
- this.tick_time = tick_time * multiple;
- this.intra_day = this.tick_time < DAY_MILLIS;
- this._findPrevious = this._findNext = findNext;
-};
-
-//Save me some carpal tunnel please.
-var _ClockP = CIQ.Market.Iterator._Clock.prototype;
-
-/**
- * Calculate the amount of minutes in a given time span.
- * This assumes hours are 24 hour format.
- *
- * NOTE! Does not know how to jump a 24 hour period, assumes that
- * oHour is always less than cHour on the same day.
- *
- * This could be done with two dates instead and remove the limitations. Not
- * sure if that is necessary at this point. We don't actually have two date
- * objects at the point that we need this number. It would take some doing to
- * figure out the date objects that would be needed.
- * @param {number} oHour The opening hour
- * @param {number} oMin The opening minute
- * @param {number} cHour The closing hour
- * @param {number} cMin The closing minute
- * @return {number} Amount of minutes in a given time span.
- * @private
- */
-_ClockP._total_minutes = function (oHour, oMin, cHour, cMin) {
- //the parens are important in this case
- return (cHour - oHour) * 60 - oMin + cMin;
-};
-
-/**
- * Create an array of minutes from the open minute to the close minute at
- * some periodicity. This array will run the entire time of the last segment
- * time segment matched.
- * @return {array} Periods
- * @private
- */
-_ClockP._alignMinutes = function () {
- if (!this.market.market_def || this.market.zopen_minute === undefined) {
- return [];
- }
- var o_min = this.market.zopen_minute;
- var match = this.market.zseg_match;
- if (match && match.adjacent_parent) {
- o_min = match.adjacent_parent.open / 60 - 1440;
- } else {
- if (this.market.isHourAligned() && this.multiple % 60 === 0) o_min = 0;
- }
- var total_minutes = this._total_minutes(
- this.market.zopen_hour,
- o_min,
- this.market.zclose_hour,
- this.market.zclose_minute
- );
- var periods = [];
- var next_minute = 0;
- while (next_minute < total_minutes) {
- periods.push(o_min + next_minute);
- next_minute += this.multiple;
- }
- return periods;
-};
-
-/**
- * Create an array of second boundaries. This only needs to be done once
- * per clock instance.
- * @param {number} max The high end of the range before wrapping back to zero.
- * @return {array} Periods
- * Example for seconds this would be 60.
- * @private
- */
-_ClockP._alignBaseZero = function (max) {
- var base = 0;
- var periods = [base];
- while (true) {
- base += this.multiple;
- if (base >= max) {
- break;
- }
- periods.push(base);
- }
- return periods;
-};
-
-/**
- * Turn this instance of the clock into a date object at the current
- * date time.
- * @return {date} A new Date object.
- * @private
- */
-_ClockP._date = function () {
- var t = Math.round(this.ctime);
- var current_date = new Date(t);
-
- if (this.intra_day) {
- this.display_date = new Date(t + this.shift_millis);
- } else {
- this.display_date = current_date;
- }
-
- return current_date;
-};
-
-/**
- * Find the boundary for minutes, seconds or milliseconds.
- * @param {array} periods A pre-calculated list of boundaries.
- * @param {number} search_for Any number to align.
- * @return {number} one of the boundaries in the array.
- * @private
- */
-_ClockP._alignToBoundary = function (periods, search_for) {
- var low = 0;
- var high = 0;
- var result = search_for;
-
- for (var ctr = 0; ctr < periods.length - 1; ctr++) {
- low = periods[ctr];
- high = periods[ctr + 1];
- if (search_for === low || search_for === high) {
- break; //already aligned;
- }
- if (search_for > low && search_for < high) {
- result = low;
- break;
- } else if (ctr + 1 === periods.length - 1) {
- //wrap around gap
- result = high;
- }
- }
- return result;
-};
-
-/**
- * Convenience for debugging.
- * @return {string} Current market date as a string
- * @private
- */
-_ClockP._peek = function () {
- return this._date().toString();
-};
-
-/**
- * When searching for open days look in hour increments.
- * Inverted.
- * @private
- */
-_ClockP._seekHr = function () {
- if (this.forward) {
- this.ctime -= HOUR_MILLIS;
- } else {
- this.ctime += HOUR_MILLIS;
- }
-};
-
-/**
- * Set this instance of the iterator clock to some date. Calls to next or
- * previous will move the clock some interval from this point in time.
- * @param {date} date Any javascript date.
- * @private
- */
-_ClockP._setStart = function (date) {
- var millis = this.market._tzDifferenceMillis(date);
- var shift_date = new Date(date.getTime() + millis);
- this.shift_millis = millis;
- this.ctime = shift_date.getTime();
- // override timezone shift
- this.shift_millis = 0;
- this.ctime = date.getTime();
-};
-
-/**
- * Regular clock move
- * @private
- */
-_ClockP._tickTock = function () {
- if (this.forward) {
- //this.ctime += (this.tick_time * this.skip);
- this.ctime += this.tick_time;
- } else {
- //this.ctime -= (this.tick_time * this.skip);
- this.ctime -= this.tick_time;
- }
-};
-
-/**
- * Inverted clock move
- * @private
- */
-_ClockP._tockTick = function () {
- if (this.forward) {
- //this.ctime -= (this.tick_time * this.skip);
- this.ctime -= this.tick_time;
- } else {
- //this.ctime += (this.tick_time * this.skip);
- this.ctime += this.tick_time;
- }
-};
-
-/**
- * Move a day at a time. Useful for finding the first open day
- * of a week or month. Always moves forward.
- * @private
- */
-_ClockP._tickTock24 = function () {
- this.ctime += DAY_MILLIS;
-};
-
-/**
- * Move a day at a time inverted. Useful for finding Sunday when
- * moving by weeks. Always moves backwards.
- * @private
- */
-_ClockP._tockTick24 = function () {
- this.ctime -= DAY_MILLIS;
-};
-
-/**
- * Wind the clock to the next open market time. If the market is already open
- * then don't move. Break out of the loop after max_iters regardless.
- * @param {function} was_open Intraday or daily function to see if the market
- * was open.
- * @param {function} wind _tockTick (inverted) or _tickTock (regular)
- * @return {boolean} True if the clock was moved
- * @private
- */
-_ClockP._windMaybe = function (was_open, wind) {
- var max = 0;
- var working_date = new Date(this.ctime);
- var moved = false;
- while (!was_open.call(this.market, working_date)) {
- wind.call(this);
- moved = true;
- working_date = new Date(this.ctime);
- max += 1;
- if (max > this.max_iters) {
- var m = "Warning! max iterations (" + this.max_iters;
- m += ") reached with no rule match.";
- console.log(m);
- break;
- }
- }
- return moved;
-};
-
-/**
- * Move the clock some number of milliseconds
- * @return {date} Current market date
- * @private
- */
-_ClockP._millisImpl = function () {
- var justAligned = false;
- if (!this.mperiods_aligned) {
- var periods = this._alignBaseZero(1000);
- var current_date = new Date(this.ctime);
- var current_millis = current_date.getMilliseconds();
- current_millis = this._alignToBoundary(periods, current_millis);
- current_date.setMilliseconds(0);
- this.ctime = current_date.getTime() + current_millis; // this allows for fractional millis
- this.mperiods_aligned = true;
- justAligned = true;
- }
- // handle market closes
- var oldMinute = this._date().getMinutes();
- this._tickTock();
- var newMinute = this._date().getMinutes();
- if (
- (justAligned || oldMinute != newMinute) &&
- !this.market._wasOpenIntraDay(this._date())
- ) {
- var seconds = this._date().getSeconds();
- var millis = this._date().getMilliseconds();
- var tickTime = this.tick_time;
- this.tick_time = 60000;
- var multiple = this.multiple;
- this.multiple = 1;
- this._minuteImpl();
- this.tick_time = tickTime;
- this.multiple = multiple;
- this.ctime += 1000 * seconds + millis;
- }
- return this._date();
-};
-
-/**
- * Move the clock some number of seconds
- * @return {date} Current market date
- * @private
- */
-_ClockP._secondImpl = function () {
- var justAligned = false;
- if (!this.speriod_aligned) {
- var periods = this._alignBaseZero(60);
- var current_date = new Date(this.ctime);
- var current_second = current_date.getSeconds();
- current_second = this._alignToBoundary(periods, current_second);
- current_date.setSeconds(current_second);
- current_date.setMilliseconds(0);
- this.ctime = current_date.getTime();
- this.speriod_aligned = true;
- justAligned = true;
- }
- // handle market closes
- var oldMinute = this._date().getMinutes();
- this._tickTock();
- var newMinute = this._date().getMinutes();
- if (
- (justAligned || oldMinute != newMinute) &&
- !this.market._wasOpenIntraDay(this._date())
- ) {
- var seconds = this._date().getSeconds();
- var tickTime = this.tick_time;
- this.tick_time = 60000;
- var multiple = this.multiple;
- this.multiple = 1;
- this._minuteImpl();
- this.tick_time = tickTime;
- this.multiple = multiple;
- this.ctime += 1000 * seconds;
- }
- return this._date();
-};
-
-/**
- * Move the clock some number of minutes. Takes into account market start time
- * and could change alignment each time it is called.
- * @return {date}
- * @private
- */
-_ClockP._minuteImpl = function () {
- var closed = this._windMaybe(this.market._wasOpenIntraDay, this._tockTick);
- var current_date = new Date(this.ctime);
- var tzOffset = current_date.getTimezoneOffset();
- var current_minute = current_date.getMinutes();
- var current_hour = current_date.getHours();
- var periods = this._alignMinutes(); //takes into account market start time
- var boundary_min =
- this._total_minutes(
- this.market.zopen_hour,
- this.market.zopen_minute,
- current_hour,
- current_minute
- ) + this.market.zopen_minute;
- if (closed) {
- if (this.forward) {
- boundary_min = periods[periods.length - 1];
- } else {
- boundary_min = periods[0];
- }
- } else {
- boundary_min = this._alignToBoundary(periods, boundary_min);
- }
- current_hour = Math.floor(boundary_min / 60) + this.market.zopen_hour;
- current_date.setHours(current_hour, boundary_min % 60, 0, 0);
- var offsetDiff = current_date.getTimezoneOffset() - tzOffset;
- if ((this.forward && offsetDiff < 0) || (!this.forward && offsetDiff > 0)) {
- //crossed a fallback timezone boundary
- current_date.setTime(current_date.getTime() - offsetDiff * 60000);
- }
- this.ctime = current_date.getTime(); //boundary aligned
- this._tickTock(); //move once
-
- var alignToHour = this.market.hour_aligned && this.multiple % 60 === 0;
-
- // Calling `_windMaybe()` will eventually cause `_was_open()` to get called, which may set
- // `this.shouldResetToBeginningOfSegment` to `true` if the clock has rolled over into a new
- // market session
- if (
- this._windMaybe(this.market._wasOpenIntraDay, this._tickTock) ||
- (!alignToHour && this.shouldResetToBeginningOfSegment)
- ) {
- current_date = new Date(this.ctime);
- if (this.forward) {
- current_date.setMinutes(this.market.zopen_minute);
- current_date.setHours(this.market.zopen_hour);
- } else {
- periods = this._alignMinutes();
- var last_boundary = periods[periods.length - 1];
- current_date.setMinutes(last_boundary % 60);
- current_date.setHours(
- Math.floor(last_boundary / 60) + this.market.zopen_hour
- );
- }
- this.ctime = current_date.getTime();
- }
- return this._date();
-};
-
-/**
- * Move the clock some number of hours.
- * @return {date}
- * @private
- */
-_ClockP._hourImpl = function () {
- this._windMaybe(this.market._wasOpenIntraDay, this._tockTick);
- var current_time = new Date(this.ctime);
- if (this.market.isHourAligned()) {
- current_time.setMinutes(0);
- } else {
- current_time.setMinutes(this.market.zopen_minute);
- }
- current_time.setSeconds(0);
- current_time.setMilliseconds(0);
- this.ctime = current_time.getTime(); //boundary aligned
- this._tickTock(); //move once
- var current_segment = this.market.zseg_match;
- if (
- this._windMaybe(this.market._wasOpenIntraDay, this._tickTock) ||
- (!this.market.hour_aligned && current_segment != this.market.zseg_match)
- ) {
- var current_date = new Date(this.ctime);
- if (this.forward) {
- current_date.setMinutes(this.market.zopen_minute);
- current_date.setHours(this.market.zopen_hour);
- } else {
- var periods = this._alignMinutes();
- var last_boundary = periods[periods.length - 1];
- current_date.setMinutes(last_boundary % 60);
- current_date.setHours(
- Math.floor(last_boundary / 60) + this.market.zopen_hour
- );
- }
- this.ctime = current_date.getTime();
- }
- return this._date();
-};
-
-/**
- * Move the clock some number of days.
- * @return {date}
- * @private
- */
-_ClockP._dayImpl = function () {
- this._windMaybe(this.market._wasOpenDaily, this._seekHr);
- var current_date = new Date(this.ctime); //closest open day
- current_date.setHours(12, 0, 0, 0);
- this.ctime = current_date.getTime(); //boundary aligned
- var ctr = 0;
- while (ctr < this.multiple) {
- if (this.forward) {
- this._tickTock24();
- } else {
- this._tockTick24();
- }
- if (!this.market._wasOpenDaily(this._date())) {
- continue;
- }
- ctr += 1;
- }
- current_date = new Date(this.ctime);
- current_date.setHours(0);
- this.ctime = current_date.getTime(); //boundary aligned
- return this._date();
-};
-
-/**
- * Move the clock some number of weeks.
- * @return {date}
- * @private
- */
-_ClockP._weekImpl = function () {
- var market = this.market;
- var current_date = new Date(this.ctime);
- current_date.setHours(12); // Stay away from DST danger zone, so we know we only go back one date each tocktick
- this.ctime = current_date.getTime();
- this._tickTock(); // move once
-
- // align to first day of week
- current_date = new Date(this.ctime);
- while (current_date.getDay() !== market.beginningDayOfWeek) {
- this._tockTick24();
- current_date = new Date(this.ctime);
- }
-
- // default to market day
- this._windMaybe(market._wasOpenDaily, this._tickTock24);
- current_date = new Date(this.ctime);
- current_date.setHours(0, 0, 0, 0);
- this.ctime = current_date.getTime(); //boundary aligned;
- return this._date();
-};
-
-/**
- * Move the clock some number of months
- * @return {date}
- * @private
- */
-_ClockP._monthImpl = function () {
- //Allow some room to account for different lengths of months.
- var current_date = new Date(this.ctime);
- current_date.setDate(15); // Stay away from month boundaries so DST doesn't foil us
- this.ctime = current_date.getTime();
-
- this._tickTock(); // move once
- current_date = new Date(this.ctime);
- //Now re align back to the first day of the month
- current_date.setDate(1);
- current_date.setHours(12); // Stay away from DST danger zone
- this.ctime = current_date.getTime();
-
- //Now find the first open day of month
- this._windMaybe(this.market._wasOpenDaily, this._tickTock24);
- current_date = new Date(this.ctime);
- current_date.setHours(0, 0, 0, 0);
- this.ctime = current_date.getTime(); //boundary aligned;
- return this._date();
-};
-
-/**
- * Search forward for the next market open
- * @param {date} date Some begin date.
- * @param {number} skip The number of intervals to move. Defaults
- * to one.
- * @return {date} A new date that has been set to the previous open of the
- * market.
- * @private
- */
-_ClockP._findNext = null;
-
-/**
- * Search backward for the next market open
- * @param {date} date Some begin date.
- * @param {number} skip The number of intervals to move. Defaults
- * to one.
- * @return {date} A new date that has been set to the previous open of the
- * market.
- * @private
- */
-_ClockP._findPrevious = null;
-
-};
-
-
-let __js_standard_nameValueStore_ = (_exports) => {
-
-/* global _CIQ, _timezoneJS, _SplinePlotter */
-
-var CIQ = typeof _CIQ !== "undefined" ? _CIQ : _exports.CIQ;
-
-/**
- * Base class for interacting with a name/value store.
- *
- * This base class saves to local storage, but you can create your own function overrides for
- * remote storage as long as you maintain the same function signatures and callback requirements.
- *
- * See {@link WebComponents.cq-views} for an implementation example.
- *
- * @constructor
- * @name CIQ.NameValueStore
- */
-CIQ.NameValueStore = CIQ.NameValueStore || function () {};
-
-CIQ.NameValueStore.prototype.toJSONIfNecessary = function (obj) {
- if (obj.constructor == String) return obj;
- try {
- var s = JSON.stringify(obj);
- return s;
- } catch (e) {
- console.log("Cannot convert to JSON: " + obj);
- return null;
- }
-};
-
-CIQ.NameValueStore.prototype.fromJSONIfNecessary = function (obj) {
- try {
- var s = JSON.parse(obj);
- return s;
- } catch (e) {
- return obj;
- }
-};
-
-/**
- * A function called after a retrieval operation on the name/value store has been completed.
- *
- * @param {object|string} error An error object or error code if data retrieval failed; null if
- * data retrieval was successful.
- * @param {object|string} response The data retrieved from storage or null if retrieval failed.
- *
- * @callback CIQ.NameValueStore~getCallback
- * @since 8.2.0
- */
-
-/**
- * A function called after an update of the name/value store has been completed.
- *
- * @param {object|string} error An error object or error code if the storage update failed; null
- * if the update was successful.
- *
- * @callback CIQ.NameValueStore~updateCallback
- * @since 8.2.0
- */
-
-/**
- * Retrieves a value from the name/value store.
- *
- * @param {string} field The field for which the value is retrieved.
- * @param {CIQ.NameValueStore~getCallback} cb A callback function called after the retrieval
- * operation has been completed. Two arguments are provided to the callback function. The
- * first argument indicates the success or failure of the operation; the second argument is
- * the value returned by the operation.
- *
- * @memberof CIQ.NameValueStore
- * @since 8.2.0 Made `cb` a required parameter; changed its type to
- * {@link CIQ.NameValueStore~getCallback}.
- *
- * @example
- * nameValueStore.get("myfield", function(err, data) {
- * if (err) {
- * // Do something with the error.
- * } else {
- * // Do something with the retrieved data.
- * }
- * });
- */
-CIQ.NameValueStore.prototype.get = function (field, cb) {
- var value = CIQ.localStorage.getItem(field);
- cb(null, this.fromJSONIfNecessary(value));
-};
-
-/**
- * Stores a value in the name/value store.
- *
- * @param {string} field The name under which the value is stored.
- * @param {string|object} value The value to store.
- * @param {CIQ.NameValueStore~updateCallback} [cb] A callback function called after the storage
- * operation has been completed. A single argument, which indicates success or failure of the
- * operation, is provided to the callback function.
- *
- * @memberof CIQ.NameValueStore
- * @since 8.2.0 Changed the type of the `cb` parameter to {@link CIQ.NameValueStore~updateCallback}.
- *
- * @example
- * nameValueStore.set("myfield", "myValue", function(err) {
- * if (err) {
- * // Do something with the error.
- * } else {
- * // Do something after the data has been stored.
- * }
- * });
- */
-CIQ.NameValueStore.prototype.set = function (field, value, cb) {
- CIQ.localStorageSetItem(field, this.toJSONIfNecessary(value));
- if (cb) cb(null);
-};
-
-/**
- * Removes a field from the name/value store.
- *
- * @param {string} field The field to remove.
- * @param {CIQ.NameValueStore~updateCallback} [cb] A callback function called after the storage
- * operation has been completed. A single argument, which indicates success or failure of the
- * operation, is provided to the callback function.
- *
- * @memberof CIQ.NameValueStore
- * @since 8.2.0 Changed the type of the `cb` parameter to {@link CIQ.NameValueStore~updateCallback}.
- *
- * @example
- * nameValueStore.remove("myfield", function(err) {
- * if (err) {
- * // Do something with the error.
- * } else {
- * // Do something after the field has been removed.
- * }
- * });
- */
-CIQ.NameValueStore.prototype.remove = function (field, cb) {
- CIQ.localStorage.removeItem(field);
- if (cb) cb(null);
-};
-
-};
-
-
-let __js_standard_quoteFeed_ = (_exports) => {
-
-/* global _CIQ, _timezoneJS, _SplinePlotter */
-
-var CIQ = typeof _CIQ !== "undefined" ? _CIQ : _exports.CIQ;
-
-if (!CIQ.ChartEngine) CIQ.ChartEngine = function () {};
-
-/**
- * See tutorial [Data Integration : Quotefeeds]{@tutorial DataIntegrationQuoteFeeds} for a complete overview and
- * step by step source code for implementing a quotefeed
- *
- * Interface for classes that implement a quotefeed. You define a quotefeed object and attach it to
- * the chart using {@link CIQ.ChartEngine#attachQuoteFeed}. Each member "fetch..." method is optional. The chart
- * will call your member method if it exists, and will skip if it does not.
- *
- * Also see {@link CIQ.ChartEngine#dontRoll} if your feed aggregates weekly and monthly bars and you do not wish the chart to roll them from daily bars.
- *
- * @name quotefeed
- * @namespace
- * @property {number} maxTicks The maximum number of ticks a quoteFeed should request at a single time. This value will be overridden if the {@link CIQ.ChartEngine.Driver} has a behavior.maximumTicks set.
- */
-function quotefeed() {}
-
-/**
- * All of your quote feed "fetch..." methods **must** call this callback function to return
- * results to the chart, even if no data is returned from your feed.
- *
- * @param {object} response Contains the results of the quote feed function that called this
- * callback function.
- * @param {string} [response.error] An error message, if one occurred.
- * @param {string} [response.suppressAlert] Set this to true to not display errors.
- * @param {array} [response.quotes] An array of quotes in required JSON format, if no error
- * occurred.
- * @param {boolean} [response.moreAvailable] Set this to false to stop pagination requests if
- * you know that no older data is available.
- * @param {boolean} [response.upToDate] Set this to true to stop forward pagination requests
- * if you know that no newer data is available.
- * @param {object} [response.attribution] This object is assigned to `stx.chart.attribution`.
- * Your UI can use this to display attribution messages. See example below.
- *
- * @callback quotefeed~dataCallback
- *
- * @example dataCallback
object.dataCallback
object.attribution
through the dataCallback
- * object.fetchUpdateData
- * once per second.
- * For example, to add a step line, you would select a [Lines]{@link CIQ.Renderer.Lines} renderer, and then set its `step` attribute, right trough the addSeries API call.
- * ```
- * stxx.addSeries(
- * "SNE",
- * {
- * renderer:"Lines",
- * step:true,
- * }
- * );
- * ```
- *
- * **Advanced Visualizations**
- *
- * Some renderers are capable of rendering *multiple series*.
- * For instance, the [Histogram]{@link CIQ.Renderer.Histogram} can display series stacked on top of one another.
- * Use `[setSeriesRenderer()]{@link CIQ.ChartEngine#setSeriesRenderer}` in this case.
- * Here is how we would create a stacked histogram from several series:
- * ```
- * var myRenderer=stxx.setSeriesRenderer(new CIQ.Renderer.Histogram({params:{subtype:"stacked"}}));
- *
- * stxx.addSeries("^NIOALL", {},
- * function() {myRenderer.attachSeries("^NIOALL","#6B9CF7").ready();}
- * );
- * stxx.addSeries("^NIOAFN", {},
- * function() {myRenderer.attachSeries("^NIOAFN","#95B7F6").ready();}
- * );
- * stxx.addSeries("^NIOAMD", {},
- * function() {myRenderer.attachSeries("^NIOAMD","#B9D0F5").ready();}
- * );
- * ```
- *
- * Example 3 - advanced stacked histogram renderer
- *
- * **Using a Symbol Object**
- *
- * The above examples all assumed your chart uses "tickers" (stock symbols).
- * We refer to complex (compound) symbols as "Symbol Objects" (see {@link CIQ.ChartEngine#loadChart}).
- * Here's how to set a series with a symbol object:
- * ```
- * stxx.addSeries(null, {color:"blue", symbolObject:yourSymbolObject});
- * ```
- *
- * **Setting a separate YAxis**
- *
- * By default, series are displayed without a y-axis.
- * They are either "overlayed" on the main chart, or if they are comparisons then they share the standard y-axis.
- * But a series can also take an optional y-axis which can be displayed on the left, or the right side of the chart.
- * To do this, you must specify parameters for a [YAxis]{@link CIQ.ChartEngine.YAxis} object and pass to addSeries:
- * ```
- * stxx.addSeries("IBM", {color:"blue", yAxis:{ position:"left" }});
- * ```
- *
- * **Understanding the relationship between [setSeriesRenderer()]{@link CIQ.ChartEngine#setSeriesRenderer} and [importLayout]{@link CIQ.ChartEngine#importLayout}**
- *
- * It is important to know that a renderer explicitly created using [setSeriesRenderer()]{@link CIQ.ChartEngine#setSeriesRenderer} will **not** be stored in the layout serialization.
- * If your implementation will require the complete restoration of a chart layout, you must instead use the syntax that includes all of the renderer parameters as part of this addSeries call.
- *
- *
- * @param {string} [id] The name of the series. If not passed then a unique ID will be assigned. (parameters.symbol and parameters.symbolObject will default to using id if they are not set explicitly *and* id is supplied.)
- * @param {object} [parameters] Parameters to describe the series. Any valid [attachSeries parameters]{@link CIQ.Renderer#attachSeries} and [renderer parameters]{@link CIQ.Renderer} will be passed to attached renderers.
- * @param {string} [parameters.renderer={@link CIQ.Renderer.Lines}] Rendering Set to the desired [renderer]{@link CIQ.Renderer} for the series.
- * - If not set, defaults to [Lines]{@link CIQ.Renderer.Lines} when `color` is set.
- * - Not needed for hidden series.
- * @param {string} [parameters.name] Rendering Set to specify renderer's name. Otherwise id will be used.
- * @param {string} [parameters.display=id/symbol] Rendering Set to the text to display on the legend. If not set, the id of the series will be used (usually symbol). If id was not provided, will default to symbol.
- * @param {string} [parameters.symbol=id] Data Loading The symbol to fetch in string format. This will be sent into the fetch() function, if no data is provided. If no symbol is provided, series will use the `id` as the symbol. If both `symbol` and `symbolObject` are set, `symbolObject` will be used.
- * @param {object} [parameters.symbolObject=id] Data Loading The symbol to fetch in object format. This will be sent into the fetch() function, if no data is provided. If no symbolObject is provided, series will use the `id` as the symbol. You can send anything you want in the symbol object, but you must always include at least a 'symbol' element. If both `symbol` and `symbolObject` are set, `symbolObject` will be used.
- * @param {string} [parameters.field=Close/Value] Data Loading Specify an alternative field to draw data from (other than the Close/Value). Must be present in your pushed data objects or returned from the quoteFeed.
- * @param {boolean} [parameters.isComparison=fasle] Rendering If set to true, shareYAxis is automatically set to true to display relative values instead of the primary symbol's price labels. {@link CIQ.ChartEngine#setComparison} is also called and set to `true`. This is only applicable when using the primary Y axis, and should only be used with internal addSeries renderers.
- * @param {boolean} [parameters.shareYAxis=false] Rendering
- * - Set to `true` so that the series shares the primary Y-axis and renders along actual values and print its corresponding current price label on the y axis.
- * - When set to `false`, the series will not be attached to a y axis. Instead it is superimposed on the chart; taking over its entire height, and maintaining the relative shape of the line. No current price will be displayed. Superimposing the ‘shape’ of one series over a primary chart, is useful when rendering multiple series that do not share a common value range.
- * - This setting will automatically override to true if 'isComparison' is set.
- * - This setting is only applicable when using the primary Y axis and has no effect when using a renderer that has its own axis.
- * @param {number} [parameters.marginTop=0] Rendering Percentage (if less than 1) or pixels (if greater than 1) from top of panel to set the top margin for the series.
**Note:** this parameter is to be used on **subsequent** series rendered on the same axis. To set margins for the first series, {@link CIQ.ChartEngine.YAxis#initialMarginTop} needs to be used.
**Note:** not applicable if shareYAxis is set.
- * @param {number} [parameters.marginBottom=0] Rendering Percentage (if less than 1) or pixels (if greater than 1) from the bottom of panel to set the bottom margin for the series.
**Note:** this parameter is to be used on **subsequent** series rendered on the same axis. To set margins for the first series, {@link CIQ.ChartEngine.YAxis#initialMarginBottom} needs to be used.
**Note:** not applicable if shareYAxis is set.
- * @param {number} [parameters.width=1] Rendering Width of line in pixels
- * @param {number} [parameters.minimum] Rendering Minimum value for the series. Overrides CIQ.minMax result.
- * @param {number} [parameters.maximum] Rendering Maximum value for the series. Overrides CIQ.minMax result.
- * @param {string} [parameters.color] Rendering Color to draw line. Will cause the line to immediately render an overlay. Only applicable for default or single colors renderers. See {@link CIQ.Renderer#attachSeries} for additional color options.
- * @param {string} [parameters.baseColor=parameters.color] Rendering Color for the base of a mountain series. Defaults to `parameters.color`.
- * @param {array|string} [parameters.pattern='solid'] Rendering Pattern to draw line, array elements are pixels on and off, or a string e.g. "solid", "dotted", "dashed"
- * @param {boolean|string} [parameters.fillGaps] Data Loading If {@link CIQ.ChartEngine#cleanupGaps} is enabled to clean gaps (not 'false'), you can use this parameter to override the global setting for this series.
- * - If `fillGaps` not present
- * - No gaps will be filled for the series.
- * - If `fillGaps` is set to 'false'
- * - No gaps will be filled for the series.
- * - If `fillGaps` is set to 'true',
- * - Gap filling will match {@link CIQ.ChartEngine#cleanupGaps}.
- * - If `fillGaps` is set to 'carry' or 'gap'
- * - Will use that filling method even if `cleanupGaps` is set differently.
- * @param {object|string} [parameters.gapDisplayStyle=true] Rendering Defines how (or if) to **render** (style) connecting lines where there are gaps in the data (missing data points), or isolated datapoints.
- * - Applicable for line-like renderers only (lines, mountains, baselines, etc).
- * - Default:
- * - `true` for standard series.
- * - `false` for comparisons.
- * - Set to `true` to use the color and pattern defined by {@link CIQ.ChartEngine#setGapLines} for the chart.
- * - Set to `false` to always show gaps.
- * - Set to an actual color string or custom color-pattern object as formatted by {@link CIQ.ChartEngine#setGapLines} to define more custom properties.
- * - 'Dots' indicating isolated items will be shown unless a `transparent` color/style is specified.
- * - If not set, and the series is a comparison, the gaps will always be rendered transparent.
- * @param {string} [parameters.fillStyle] Rendering Fill style for mountain chart (if selected). For semi-opaque use rgba(R,G,B,.1). If not provided a gradient is created with color and baseColor.
- * @param {boolean} [parameters.permanent=false] Rendering Set to `true` to activate. Makes series unremoveable by a user **when attached to the default renderer**. If explicitly linked to a renderer, see {@link CIQ.Renderer#attachSeries} for details on how to prevent an attached series from being removed by a user.
- * @param {object} [parameters.data] Data Loading Data source for the series.
- * If this field is omitted, the library will connect to the QuoteFeed (if available) to fetch initial data ( unless `parameters.loadData` is set to `false`), and manage pagination and updates.
- * If data is sent in this field, it will be loaded into the masterData, but series will **not** be managed by the QuoteFeed (if available) for pagination or updates.
- * Items in this array *must* be ordered from earliest to latest date.
- * Accepted formats:
- *
**Full OHLC:**
- * An array of properly formatted OHLC quote object(s). [See OHLC Data Format]{@tutorial InputDataFormat}.
- *
----
**Price Only:**
- * An array of objects, each one with the followng elements:
- * @param {date} [parameters.data.DT] JavaScript date object or epoch representing data point (overrides Date parameter if present)
- * @param {string} [parameters.data.Date] string date representing data point ( only used if DT parameter is not present)
- * @param {number} parameters.data.Value value of the data point ( As an alternative, you can send `parameters.data.Close` since your quote feed may already be returning the data using this element name)
- * @param {string|boolean} [parameters.panel] Rendering The panel name on which the series should display. If the panel doesn't exist, one will be created. If `true` is passed, a new panel will also be created.
- * @param {string} [parameters.action='add-series'] Rendering Overrides what action is sent in symbolChange events. Set to null to prevent a symbolChange event.
- * @param {boolean} [parameters.loadData=true] Data Loading Include and set to false if you know the initial data is already in the masterData array or will be loaded by another method. The series will be added but no data requested. Note that if you remove this series, the data points linked to it will also be removed which may create issues if required by the chart. If that is the case, you will need to manually remove from the renderer linked to it instead of the underlying series itself.
- * @param {boolean} [parameters.extendToEndOfDataSet] Rendering Set to true to plot any gap at the front of the chart. Automatically done for step charts (set to false to disable) or if parameters.gapDisplayStyle are set (see {@link CIQ.ChartEngine#addSeries})
- * @param {boolean} [parameters.displayFloatingLabel=false] Rendering Set to false to disable the display of a Y-axis floating label for this series.
- * @param {boolean|object} [parameters.baseline] Rendering If a boolean value, indicates whether the series renderer draws a baseline. If an object, must be the equivalent of {@link CIQ.ChartEngine.Chart#baseline}.
- * @param {function} [cb] Callback function to be executed once the fetch returns data from the quoteFeed. It will be called with an error message if the fetch failed: `cb(err);`. Only applicable if no data is provided.
- * @return {object} The series object.
- *
- * @memberof CIQ.ChartEngine
- * @since
- * - 04-2015 If `isComparison` is true shareYAxis is automatically set to true and setComparison(true) called. createDataSet() and draw() are automatically called to immediately render the series.
- * - 15-07-01 If `color` is defined and chartStyle is not set then it is automatically set to "line".
- * - 15-07-01 Ability to use setSeriesRenderer().
- * - 15-07-01 Ability to automatically initialize using the quoteFeed.
- * - 15-07-01 `parameters.quoteFeedCallbackRefresh` no longer used. Instead if `parameters.data.useDefaultQuoteFeed` is set to `true` the series will be initialized and refreshed using the default quote feed. (Original documentation: `{boolean} [parameters.quoteFeedCallbackRefresh]` Set to true if you want the series to use the attached quote feed (if any) to stay in sync with the main symbol as new data is fetched (only available in Advanced package).)
- * - 2015-11-1 `parameters.symbolObject` is now available.
- * - 05-2016-10 `parameters.forceData` is now available.
- * - 09-2016-19 `parameters.data.DT` can also take an epoch number.
- * - 09-2016-19 `parameters.data.useDefaultQuoteFeed` no longer used. If no `parameters.data` is provided the quotefeed will be used.
- * - 3.0.8 `parameters.forceData` no longer used, now all data sent in will be forced.
- * - 3.0.8 `parameters.loadData` added.
- * - 4.0.0 Added `parameters.symbol` (string equivalent of parameters.symboObject).
- * - 4.0.0 Multiple series can now be added for the same underlying symbol. parameters.field or parameters.symbolObject can be used to accomplish this.
- * - 4.0.0 Added `parameters.baseColor`.
- * - 5.1.0 Series data now added to masterData as an object. This allows storage of more than just one data point, facilitating OHLC series!
- * - 5.1.0 addSeries will now create a renderer unless renderer, name and color parameters are all omitted.
- * - 5.1.0 Now also dispatches a "symbolChange" event when pushing data into the chart, rather than only when using a quote feed.
- * - 5.1.1 Added `parameters.extendToEndOfDataSet`.
- * - 5.1.1 `parameters.chartType`, originally used to draw "mountain" series, has been deprecated in favor of the more flexible 'renderer' parameter. It is being maintained for backwards compatibility.
- * - 5.2.0 `parameters.gaps` has been deprecated (but maintained for backwards compatibility) and replaced with `parameters.gapDisplayStyle`.
- * - 6.0.0 `parameters.fillGaps` is now a string type and can accept either "carry" or "gap". Setting to true will use the value of stxx.cleanupGaps.
- * - 6.2.0 No longer force 'percent'/'linear', when adding/removing comparison series, respectively, unless {@link CIQ.ChartEngine.Chart#forcePercentComparison} is true. This allows for backwards compatibility with previous UI modules.
- * - 6.3.0 If a panel name is passed into the function, a new panel will be created if one doesn't already exist.
- * - 6.3.0 Added `parameters.displayFloatingLabel`.
- * - 8.1.0 Supports custom baselines. See example.
- * - 8.2.0 Added `parameters.baseline`.
- *
- * @example
- * Comparison namespace
- * @namespace
- * @name CIQ.Comparison
- */
-CIQ.Comparison = CIQ.Comparison || function () {}; // Create namespace
-
-/**
- * For relative comparisons, this is the starting (baseline) point.
- *
- * Valid options are:
- * - A number to specify an absolute amount to be used as the starting value for all percentage changes.
- * - A string containing the symbol of an existing series to be used as the starting value for the comparisons (for instance "IBM"). Computations will then be based on the change from the first visible bar value for the selected symbol.
- * - An empty string will compare against the baseline value of the main series (same as in "percent" scale).
- *
- * See {@link CIQ.ChartEngine#setChartScale} for more details.
- * @type number | string
- * @memberof CIQ.Comparison
- * @since 5.1.0
- */
-CIQ.Comparison.initialPrice = 100;
-
-/**
- * Used to compute the initial price when it is supplied as a string
- * @param {CIQ.ChartEngine.Chart} chart The specific chart
- * @return {number} The initial price as a number
- * @memberof CIQ.Comparison
- * @since 5.1.0
- * @private
- */
-CIQ.Comparison.getInitialPrice = function (chart) {
- if (chart.initialComparisonPrice) return chart.initialComparisonPrice;
- chart.initialComparisonPrice = 100;
- var symbol = CIQ.Comparison.initialPrice;
- if (typeof symbol == "number") chart.initialComparisonPrice = symbol; // absolute amount
- if (typeof symbol == "string") {
- if (chart.series[symbol] || symbol === "") {
- var priceField = "Close";
- if (chart.defaultPlotField) {
- if (!chart.highLowBars) priceField = chart.defaultPlotField;
- }
- for (
- var i = chart.dataSet.length - chart.scroll - 1;
- i < chart.dataSet.length;
- i++
- ) {
- var bar = chart.dataSet[i];
- if (bar) {
- if (bar[symbol] && bar[symbol][priceField]) {
- chart.initialComparisonPrice = bar[symbol][priceField];
- break;
- } else if (symbol === "" && bar[priceField]) {
- chart.initialComparisonPrice = bar[priceField];
- break;
- }
- }
- }
- }
- }
- return chart.initialComparisonPrice;
-};
-
-/**
- * Transform function for percent comparison charting
- * @param {CIQ.ChartEngine} stx The charting object
- * @param {CIQ.ChartEngine.Chart} chart The specific chart
- * @param {number} price The price to transform
- * @return {number} The transformed price (into percentage)
- * @memberof CIQ.Comparison
- */
-CIQ.Comparison.priceToPercent = function (stx, chart, price) {
- var baseline = CIQ.Comparison.baseline || price;
- return Math.round(((price - baseline) / baseline) * 100 * 10000) / 10000;
-};
-
-/**
- * Untransform function for percent comparison charting
- * @param {CIQ.ChartEngine} stx The charting object
- * @param {CIQ.ChartEngine.Chart} chart The specific chart
- * @param {number} percent The price to untransform
- * @return {number} The untransformed price
- * @memberof CIQ.Comparison
- */
-CIQ.Comparison.percentToPrice = function (stx, chart, percent) {
- var baseline = CIQ.Comparison.baseline || 1;
- return baseline * (1 + percent / 100);
-};
-
-/**
- * Transform function for relative comparison charting
- * @param {CIQ.ChartEngine} stx The charting object
- * @param {CIQ.ChartEngine.Chart} chart The specific chart
- * @param {number} price The price to transform
- * @return {number} The transformed price (relative to {@link CIQ.Comparison.initialPrice})
- * @memberof CIQ.Comparison
- * @since 5.1.0
- */
-CIQ.Comparison.priceToRelative = function (stx, chart, price) {
- var baseline = CIQ.Comparison.baseline || price;
- var initialPrice = CIQ.Comparison.getInitialPrice(chart);
- return (initialPrice * price) / baseline;
-};
-
-/**
- * Untransform function for relative comparison charting
- * @param {CIQ.ChartEngine} stx The charting object
- * @param {CIQ.ChartEngine.Chart} chart The specific chart
- * @param {number} relative The price to untransform
- * @return {number} The untransformed price
- * @memberof CIQ.Comparison
- * @since 5.1.0
- */
-CIQ.Comparison.relativeToPrice = function (stx, chart, relative) {
- var baseline = CIQ.Comparison.baseline || 1;
- var initialPrice = CIQ.Comparison.getInitialPrice(chart);
- return (baseline * relative) / initialPrice;
-};
-
-CIQ.Comparison.createComparisonSegmentInner = function (stx, chart) {
- // create an array of the fields that we're going to compare
- var fields = [];
- var field, panel, yAxis;
- for (field in chart.series) {
- var parameters = chart.series[field].parameters;
- if (parameters.isComparison) {
- fields.push(parameters.symbol);
- }
- }
- var priceFields = ["Close", "Open", "High", "Low", "iqPrevClose"];
- var highLowBars = stx.chart.highLowBars;
- if (chart.defaultPlotField && !highLowBars)
- priceFields.unshift(chart.defaultPlotField);
- var baselineField = priceFields[0];
- var s = stx.layout.studies;
- for (var n in s) {
- var sd = s[n];
- panel = stx.panels[sd.panel];
- yAxis = sd.getYAxis(stx);
- if (!panel || panel.yAxis != yAxis) continue;
- for (field in sd.outputMap) priceFields.push(field);
- for (var h = 0; h <= 2; h++)
- priceFields.push(sd.name + "_hist" + (h ? h : ""));
- if (sd.referenceOutput)
- priceFields.push(sd.referenceOutput + " " + sd.name);
- }
- for (var p in stx.plugins) {
- var plugin = stx.plugins[p];
- if (!plugin.transformOutputs) continue;
- for (field in plugin.transformOutputs) {
- priceFields.push(field);
- }
- }
-
- chart.initialComparisonPrice = null;
- chart.dataSegment = [];
- var firstQuote = null;
-
- // By default start comparison at the close of the previous bar
- var firstTick = chart.dataSet.length - chart.scroll - 1;
- // Start at first visible bar instead if flag is set
- if (stx.startComparisonsAtFirstVisibleBar) firstTick += 1;
-
- //if(stx.micropixels+stx.layout.candleWidth/2<0) firstTick++; // don't baseline comparison with a bar off the left edge
- var transformsToProcess = chart.maxTicks + 3; //make sure we have transformed enough data points that we plot the y-axis intercept correctly
-
- for (var i = 0; i <= transformsToProcess; i++) {
- if (i == transformsToProcess) i = -1; //go back and revisit the tick before the first
- var position = firstTick + i;
- if (position < chart.dataSet.length && position >= 0) {
- var quote = chart.dataSet[position];
- var closingPrice = quote[baselineField];
-
- if (!firstQuote) {
- if (closingPrice === 0 || closingPrice === null) {
- if (i < 0) break;
- //if we still can't get a single tick to do this and we try to revisit, we are out, or we go into infinite loop
- else continue; // can't calculate the percentage gain/loss if the close is 0 or null.
- }
- firstQuote = CIQ.clone(quote);
- }
-
- // iterate through the fields calculating the percentage gain/loss
- // We store the results in the "transform" subobject of the data set
- // Note we inline the math calculation to save overhead of JS function call
- if (!quote.transform)
- quote.transform = {
- cache: {},
- DT: quote.DT,
- Date: quote.Date
- };
- if (!CIQ.Comparison.baseline && closingPrice)
- firstQuote = CIQ.clone(quote);
- CIQ.Comparison.baseline = firstQuote[baselineField];
-
- var j;
- for (j = 0; j < priceFields.length; j++) {
- field = priceFields[j];
- if (quote[field] || quote[field] === 0)
- //quote.transform[field]=Math.round(((quote[field]-CIQ.Comparison.baseline)/CIQ.Comparison.baseline*100)*10000)/10000; // first compute the close pct, our baseline
- quote.transform[field] = chart.transformFunc(
- stx,
- chart,
- quote[field]
- );
- }
-
- // Transform the series
- for (j = 0; j < fields.length; j++) {
- field = fields[j];
- var compSymbol = chart.series[field];
- if (i == -1 && compSymbol && compSymbol.parameters.isComparison) {
- delete quote.transform[field];
- continue;
- }
- var seriesData = quote[field];
- for (var k = 0; seriesData && k < priceFields.length; k++) {
- var seriesPrice = seriesData[priceFields[k]];
- if (seriesPrice || seriesPrice === 0) {
- // Skip blanks
- var baseline =
- firstQuote[field] && firstQuote[field][priceFields[0]];
- if (!baseline && baseline !== 0) {
- // This takes care of a series that starts part way through the chart
- // The baseline is then computed looking back to what it would have been with a 0% change
- if (!firstQuote[field]) firstQuote[field] = {};
- firstQuote[field][priceFields[k]] = baseline =
- (seriesPrice * CIQ.Comparison.baseline) / quote[baselineField];
- }
- if (baseline !== 0) {
- var masterBaseline = CIQ.Comparison.baseline || 1;
- var rationalizedPrice = seriesPrice * (masterBaseline / baseline);
- if (!quote.transform[field]) quote.transform[field] = {};
- quote.transform[field][priceFields[k]] = chart.transformFunc(
- stx,
- chart,
- rationalizedPrice
- );
- }
- }
- }
- }
- chart.dataSegment.push(quote);
- } else if (position < 0) {
- chart.dataSegment.push(null);
- }
- if (i < 0) break; //we revisited tick before first so we are done
- }
-};
-
-/**
- * Formats the percentage values on the comparison chart
- * @param {CIQ.ChartEngine} stx The chart object
- * @param {CIQ.ChartEngine.Panel} panel The panel
- * @param {number} price The raw percentage as a decimal
- * @return {string} The percentage formatted as a percent (possibly using localization if set in stx)
- * @memberof CIQ.Comparison
- */
-CIQ.Comparison.priceFormat = function (stx, panel, price) {
- if (price === null || typeof price == "undefined" || isNaN(price)) return "";
- var priceTick = panel.yAxis.priceTick;
- var internationalizer = stx.internationalizer;
- if (internationalizer) {
- if (priceTick >= 5) price = internationalizer.percent.format(price / 100);
- else if (priceTick >= 0.5)
- price = internationalizer.percent1.format(price / 100);
- else if (priceTick >= 0.05)
- price = internationalizer.percent2.format(price / 100);
- else if (priceTick >= 0.005)
- price = internationalizer.percent3.format(price / 100);
- else price = internationalizer.percent4.format(price / 100);
- } else {
- if (priceTick >= 5) price = price.toFixed(0) + "%";
- else if (priceTick >= 0.5) price = price.toFixed(1) + "%";
- else if (priceTick >= 0.05) price = price.toFixed(2) + "%";
- else if (priceTick >= 0.005) price = price.toFixed(3) + "%";
- else price = price.toFixed(4) + "%";
- }
- if (parseFloat(price) === 0 && price.charAt(0) == "-") {
- // remove minus sign from -0%, -0.0%, etc
- price = price.substring(1);
- }
- return price;
-};
-
-/**
- * Turns comparison charting on or off and sets the transform.
- *
- * Should not be called directly. Either use the {@link CIQ.ChartEngine#addSeries} `isComparison` parameter or use {@link CIQ.ChartEngine#setChartScale}
- *
- * @param {string|boolean} mode Type of comparison ("percent" or "relative").
- * - Setting to true will enable "percent".
- * - Setting to "relative" will allow the comparisons to be rendered in relation to any provided 'basis' value. For example, the previous market day close price.
- * @param {CIQ.ChartEngine.Chart} [chart] The specific chart for comparisons
- * @param {number|string} [basis] For a "relative" mode, the basis to relate to. Can be a number or a string. If a string, will use the first price in the datasegment for the series keyed by the string. Sets {@link CIQ.Comparison.initialPrice}.
- * @memberof CIQ.ChartEngine
- * @since
- * - 04-2015 Signature has been revised.
- * - 5.1.0 Signature revised again, added basis.
- * - 5.1.0 `mode` now also supports "relative" to allow comparisons to be rendered in relation to any provided value.
- */
-CIQ.ChartEngine.prototype.setComparison = function (mode, chart, basis) {
- if (!chart) chart = this.chart;
- if (typeof chart == "string") chart = this.charts[chart];
- if (basis || basis === "") CIQ.Comparison.initialPrice = basis;
- if (mode === true) {
- // backward compatibility, older versions uses a true/false switch because they did not support the developer setting arbitrary baseline values
- if (chart.isComparison) return; // Do nothing if it's already turned on
- mode = "percent";
- }
- this.resetDynamicYAxis();
- var yAxis = chart.panel.yAxis;
- var wasComparison = yAxis.priceFormatter == CIQ.Comparison.priceFormat; // tests if the current formatter is a comparison formatter
- // this is like testing if the previous mode was "percent"
- switch (mode) {
- case "relative":
- this.setTransform(
- chart,
- CIQ.Comparison.priceToRelative,
- CIQ.Comparison.relativeToPrice
- );
- if (wasComparison) {
- yAxis.priceFormatter = yAxis.originalPriceFormatter
- ? yAxis.originalPriceFormatter.func
- : null;
- yAxis.originalPriceFormatter = null;
- }
- yAxis.whichSet = "dataSegment";
- chart.isComparison = true;
- break;
- case "percent":
- this.setTransform(
- chart,
- CIQ.Comparison.priceToPercent,
- CIQ.Comparison.percentToPrice
- );
- if (!wasComparison) {
- yAxis.originalPriceFormatter = { func: yAxis.priceFormatter };
- yAxis.priceFormatter = CIQ.Comparison.priceFormat;
- }
- yAxis.whichSet = "dataSegment";
- chart.isComparison = true;
- break;
- default:
- this.unsetTransform(chart);
- if (wasComparison) {
- yAxis.priceFormatter = yAxis.originalPriceFormatter
- ? yAxis.originalPriceFormatter.func
- : null;
- yAxis.originalPriceFormatter = null;
- }
- yAxis.whichSet = "dataSet";
- chart.isComparison = false;
- break;
- }
-};
-
-/**
- * Sets the chart scale.
- * @param {string} chartScale
- * - Available options:
- * - "log"
- * > The logarithmic scale can be helpful when the data covers a large range of values – the logarithm reduces this to a more manageable range.
- * - "linear"
- * > This is the standard y axis scale; where actual prices are displayed in correlation to their position on the axis, without any conversions applied.
- * - "percent"
- * > Calculations for the "percent" scale, used by comparisons, are based on the change between the first visible bar to the last visible bar.
- * > This is so you can always see relevant information regardless of period.
- * > Let's say you are looking at a chart showing a range for the current month. The change will be the difference from the beginning of the month to today.
- * > If you now zoom or change the range to just see this past week, then the change will reflect that change from the first day of the week to today.
- * > This is how most people prefer to see change, sine it is dynamically adjusted to the selected range. If you want to see today's change, just load today's range.
- * > Keep in mind that there is a difference between the change from the beginning of the day, and the change from the beginning of the trading day. So be careful to set the right range.
- * - "relative"
- * > Very similar to 'percent' but the baseline value can be explicitly set.
- * > This is useful if you wish to baseline your comparisons on secondary series, or even a hard coded value ( ie: opening price for the day).
- * >
See {@link CIQ.Comparison.initialPrice} for details on how to set basis for "relative" scale.
- *
- * - Setting to "percent" or "relative" will call {@link CIQ.ChartEngine#setComparison} even if no comparisons are present; which sets `stxx.chart.isComparison=true`.
- * - To check if scale is in percentage mode use `stxx.chart.isComparison` instead of using the {@link CIQ.ChartEngine#chartScale} value.
- * - See {@link CIQ.ChartEngine.Chart#forcePercentComparison} for behavior of automatic scale setting and removal for [comparisons]{@link CIQ.ChartEngine#addSeries}.
- * @memberof CIQ.ChartEngine
- * @since
- * - 4.1.0 Added "percent".
- * - 5.1.0 Added "relative".
- */
-CIQ.ChartEngine.prototype.setChartScale = function (chartScale) {
- var chart = this.chart;
- var needsTransform = {
- percent: true,
- relative: true
- };
- if (!chartScale) chartScale = "linear";
- if (needsTransform[chartScale]) {
- this.setComparison(chartScale, chart, CIQ.Comparison.initialPrice);
- } else if (needsTransform[this.layout.chartScale]) {
- this.setComparison(false, chart);
- }
- this.layout.chartScale = chartScale;
- if (chart.canvas) this.draw();
- this.changeOccurred("layout");
-};
-
-};
-
-
-let __js_standard_share_ = (_exports) => {
-
-/* global _CIQ, _timezoneJS, _SplinePlotter */
-
-/* global html2canvas, requirejs */
-
-var CIQ = typeof _CIQ !== "undefined" ? _CIQ : _exports.CIQ;
-
-var h2canvas;
-
-/**
- * Manages chart sharing and uploading.
- *
- * See the {@tutorial Chart Sharing} tutorial for more details.
- *
- * @constructor
- * @name CIQ.Share
- */
-CIQ.Share = CIQ.Share || function () {};
-
-/**
- * Creates a png image or canvas of the current chart and everything inside the container associated with the chart when it was instantiated; including HTML.
- * Elements outside the chart container will **NOT** be included.
- *
- * It will dynamically try to load `js/thirdparty/html2canvas.min.js` if not already loaded.
- *
- * This function is asynchronous and requires a callback function. The callback will be passed
- * a data object or canvas which can be sent to a server or converted to an image.
- *
- * By default this method will rely on HTML2Canvas to create an image which will rely on Promises. If your browser does not implement Promises, be sure to include a polyfill to ensure HTML2Canvas works properly.
- *
- * **This method does not always work with React or Safari**
- *
- * **Canvases can only be exported if all the contents including CSS images come from the same domain,
- * or all images have cross origin set properly and come from a server that supports CORS; which may or may not be possible with CSS images.**
- *
- * @param {CIQ.ChartEngine} stx Chart object
- * @param {object} params
- * @param {number} params.width
- * @param {number} params.height
- * @param {string} params.background
- * @param {boolean} params.data If true returns the image data, otherwise, it returns the canvas
- * @param {Array} params.hide Array of strings; array of the CSS selectors of the DOM elements to hide, before creating a PNG
- * @param {Function} cb Callback when image is available fc(error,data) where data is the serialized image object or canvas
- * @name CIQ.Share.FullChart2PNG
- * @since 4.0.0 Addition of `params.hide`.
- * @version ChartIQ Advanced Package plug-in
- * @private
- */
-CIQ.Share.FullChart2PNG = function (stx, params, cb) {
- if (!stx || !stx.chart) return;
- //If we haven't loaded html2canvas, load it
- if (typeof html2canvas === "undefined")
- return loadHTML2Canvas(function () {
- return createHTML2Canvas(stx, params, cb);
- });
- h2canvas = html2canvas;
- createHTML2Canvas(stx, params, cb);
-};
-
-function inlineStyle(elem) {
- if (!elem.style) return;
- var styles = getComputedStyle(elem);
- var props = [
- "alignment-baseline",
- "dominant-baseline",
- "fill",
- "fill-opacity",
- "font-family",
- "font-size",
- "font-variant",
- "font-weight",
- "text-align",
- "text-anchor"
- ];
- props.forEach(function (i) {
- if (!elem.style[i] && styles[i]) elem.style[i] = styles[i];
- });
- for (var child in elem.children) {
- inlineStyle(elem.children[child]);
- }
-}
-
-function createHTML2Canvas(stx, params, cb) {
- if (!params) params = {};
- var recordsTurnedOff = [],
- ciqNoShare = "ciq-no-share",
- body = document.querySelector("body");
-
- if (params.hide && params.hide instanceof Array) {
- var customHide = params.hide.join(", ");
- var hideItems = document.querySelectorAll(customHide);
- for (var idx = 0; idx < hideItems.length; idx++) {
- hideItems[idx].classList.add(ciqNoShare);
- }
- }
- // Combining ".sharing" and ".ciq-no-share" to display:none for selected elements
- body.classList.add("sharing");
-
- // explicitly set svg text-related attributes
- var svgs = stx.chart.container.getElementsByTagName("svg");
- var svgOriginalSources = [];
- var svgIndex = 0;
- for (; svgIndex < svgs.length; svgIndex++) {
- var svg = svgs[svgIndex];
- svgOriginalSources.push(svg.innerHTML);
- inlineStyle(svg);
- }
-
- // Safari does not support SVG pattern fills. So we skip optimization in html2canvas third party file.
- // (we've modified the resizeImage() function to detect "iPad" in user agent)
-
- h2canvas(stx.chart.container, {
- allowTaint: false,
- logging: false,
- width: params.width || null,
- height: params.height || null,
- backgroundColor: params.background || null,
- useCORS: true
- })
- .then(function (canvas) {
- if (cb) {
- //return the full canvas if the data param is not true
- cb(null, params.data ? canvas.toDataURL("image/png") : canvas);
- }
- for (svgIndex = 0; svgIndex < svgs.length; svgIndex++) {
- svgs[svgIndex].innerHTML = svgOriginalSources[svgIndex];
- }
- body.classList.remove("sharing");
- })
- .catch(function (error) {
- if (cb) cb(error);
- for (svgIndex = 0; svgIndex < svgs.length; svgIndex++) {
- svgs[svgIndex].innerHTML = svgOriginalSources[svgIndex];
- }
- body.classList.remove("sharing");
- });
-}
-
-//Load HTML2Canvas dynamically. If html2canvas.min.js is already loaded (statically, webpacked or with require.js) then this will be skipped.
-// HTML2Canvas is rather heavy which is why we provide the option to load dynamically. It isn't really necessary to load this until
-// a user actually shares a chart.
-function loadHTML2Canvas(cb) {
- //Make sure HTML2Canvas is not already loaded
- if (typeof html2canvas === "undefined") {
- //If we have require, use it
- if (typeof requirejs !== "undefined") {
- try {
- return requirejs(["html2canvas.min.js"], function (h2) {
- h2canvas = h2;
- return cb();
- });
- } catch (exception) {
- console.warn(
- "Require loading has failed, attempting to load html2canvas manually."
- );
- }
- }
-
- // if no require then load directly
- CIQ.loadScript(getMyRoot() + "html2canvas.min.js", function () {
- h2canvas = html2canvas;
- return cb();
- });
- } else {
- h2canvas = html2canvas;
- return cb();
- }
-}
-
-//Get the location of this file. Unbundled, this would be share.js. Bundled, this would be standard.js. When unbundled
-//we need to walk back up out of advanced. When bundled we don't need a root because thirdparty should be a relative
-//path.
-//Set CIQ.Share.html2canvasLocation to completely override this logic.
-function getMyRoot() {
- if (CIQ.Share.html2canvasLocation) return CIQ.Share.html2canvasLocation;
- var sc = document.getElementsByTagName("script");
- for (var idx = 0; idx < sc.length; idx++) {
- var s = sc[idx];
- if (s.src && s.src.indexOf("share.js") > -1) {
- return s.src.replace(/standard\/share\.js/, "") + "thirdparty/";
- }
- }
- return "js/thirdparty/";
-}
-
-/**
- * Creates a png image of the current chart and everything inside the container associated with the chart when it was instantiated; including HTML.
- * Elements outside the chart container will **NOT** be included.
- *
- * If widthPX and heightPX are passed in then the image will be scaled to the requested dimensions.
- *
- * It will dynamically try to load `js/thirdparty/html2canvas.min.js` if not already loaded.
- *
- * This function is asynchronous and requires a callback function.
- * The callback will be passed a data object or canvas which can be sent to a server or converted to an image.
- *
- * Important Notes:
- * - **This method will rely on Promises. If your browser does not implement Promises, be sure to include a polyfill.**
- *
- * - **This method does not always work with React or Safari**
- *
- * - **Canvases can only be exported if all the contents including CSS images come from the same domain,
- * or all images have cross origin set properly and come from a server that supports CORS; which may or may not be possible with CSS images.**
- *
- * - **When using the charts from `file:///`, make sure to include `html2canvas` statically instead of allowing this method to load it dynamically.**
- *
Example:
- *
``
- *
- * @param {object} stx Chart object
- * @param {object} [params] Parameters to describe the image.
- * @param {number} [params.widthPX] Width of image to create. If passed then params.heightPX will adjust to maintain ratio.
- * @param {number} [params.heightPX] Height of image to create. If passed then params.widthPX will adjust to maintain ratio.
- * @param {string} [params.imageType] Specifies the file format your image will be output in. The dfault is PNG and the format must be suported by your browswer.
- * @param {Array} [params.hide] Array of strings; array of the CSS selectors of the DOM elements to hide, before creating a PNG
- * @param {Function} cb Callback when image is available fc(data) where data is the serialized image object
- * @memberOf CIQ.Share
- * @since
- * - 3.0.0 Function signature changed to take parameters.
- * - 4.0.0 Addition of `parameters.hide`.
- * @version ChartIQ Advanced Package plug-in
- */
-CIQ.Share.createImage = function (stx, params, cb) {
- var args = [].slice.call(arguments);
- cb = args.pop();
- //imageType is in its location so developers don't need to change their current code.
- if (params === null || typeof params != "object")
- params = { widthPX: args[1], heightPX: args[2], imageType: args[3] };
- var widthPX = params.widthPX;
- var heightPX = params.heightPX;
- var imageType = params.imageType;
-
- // Set background for any part of canvas that is currently transparent NO LONGER NECESSARY????
- // CIQ.fillTransparentCanvas(stx.chart.context, stx.containerColor, stx.chart.canvas.width, stx.chart.canvas.height);
-
- // We use style height/width instead of the canvas width/height when the backing store is 2x on retina screens
- var renderedHeight = stx.chart.canvas.height;
- var renderedWidth = stx.chart.canvas.width;
- if (stx.chart.canvas.style.height) {
- renderedHeight = CIQ.stripPX(stx.chart.canvas.style.height);
- renderedWidth = CIQ.stripPX(stx.chart.canvas.style.width);
- }
- if (widthPX && heightPX) {
- renderedHeight = heightPX;
- renderedWidth = widthPX;
- } else if (heightPX) {
- renderedWidth =
- stx.chart.canvas.width * (renderedHeight / stx.chart.canvas.height);
- } else if (widthPX) {
- renderedWidth = widthPX;
- renderedHeight =
- stx.chart.canvas.height * (widthPX / stx.chart.canvas.width);
- }
- //var totalHeight=renderedHeight;
- var imageResult = imageType ? "image/" + imageType : "image/png";
- // Render the canvas as an image
- var shareImage = document.createElement("img");
- shareImage.onload = function () {
- // Print the image on a new canvas of appropriate size
- CIQ.Share.FullChart2PNG(
- stx,
- {
- image: this,
- width: renderedWidth,
- height: renderedHeight,
- hide: params.hide
- },
- function (err, canvas) {
- if (err) {
- console.warn("Error producing canvas snapshot: " + err);
- } else {
- try {
- cb(canvas.toDataURL(imageResult)); // return the data
- } catch (e) {
- console.warn(
- "Safari devices do not handle CORS enabled images. Using the charts' canvas as a fallback."
- );
- cb(shareImage.src);
- }
- }
- }
- );
- };
- shareImage.src = stx.chart.canvas.toDataURL(imageResult);
-};
-
-/**
- * Uploads an image to a server. The callback will take two parameters. The first parameter is an error
- * condition (server status), or null if there is no error. The second parameter (if no error) will contain
- * the response from the server.
- * 'payload' is an optional object that contains meta-data for the server. If payload exists then the image will be added as a member of the payload object, otherwise an object will be created
- * 'dataImage' should be a data representation of an image created by the call canvas.toDataURL such as is returned by CIQ.Share.createImage
- * If you are getting a status of zero back then you are probably encountering a cross-domain ajax issue. Check your access-control-allow-origin header on the server side
-
- * @param {string} dataImage Serialized data for image
- * @param {string} url URL to send the image
- * @param {object} [payload] Any additional data to send to the server should be sent as an object.
- * @param {Function} cb Callback when image is uploaded
- * @memberOf CIQ.Share
- * @version ChartIQ Advanced Package plug-in
- */
-CIQ.Share.uploadImage = function (dataImage, url, payload, cb) {
- if (!payload) payload = {};
- payload.image = dataImage;
- var valid = CIQ.postAjax(url, JSON.stringify(payload), function (
- status,
- response
- ) {
- if (status != 200) {
- cb(status, null);
- return;
- }
- cb(null, response);
- });
- if (!valid) cb(0, null);
-};
-
-/**
- * Convenience function that serves as a wrapper for createImage and uploadImage.
- * It will create an image using the default parameters. If you wish to customize the image you must use {@link CIQ.Share.createImage} separately and then call {@link CIQ.Share.uploadImage}.
- * @param {object} stx Chart Object
- * @param {object} [override] Parameters that overwrite the default hosting location from https://share.chartiq.com to a custom location.
- * @param {object} [override.host]
- * @param {object} [override.path]
- * @param {function} cb Callback when the image is uploaded.
- * @memberof CIQ.Share
- * @since 2015-11-01
- * @example
- * // here is the exact code this convenience function is using
- CIQ.Share.createImage(stx, {}, function(imgData){
- var id=CIQ.uniqueID();
- var host="https://share.chartiq.com";
- var url= host + "/upload/" + id;
- if(override){
- if(override.host) host=override.host;
- if(override.path) url=host+override.path+"/"+id;
- }
- var startOffset=stx.getStartDateOffset();
- var metaData={
- "layout": stx.exportLayout(),
- "drawings": stx.exportDrawings(),
- "xOffset": startOffset,
- "startDate": stx.chart.dataSegment[startOffset].Date,
- "endDate": stx.chart.dataSegment[stx.chart.dataSegment.length-1].Date,
- "id": id,
- "symbol": stx.chart.symbol
- };
- var payload={"id": id, "image": imgData, "config": metaData};
- CIQ.Share.uploadImage(imgData, url, payload, function(err, response){
- if(err!==null){
- CIQ.alert("error sharing chart: ",err);
- }else{
- cb(host+response);
- }
- });
- // end sample code to upload image to a server
- });
- *
- */
-CIQ.Share.shareChart = function (stx, override, cb) {
- CIQ.Share.createImage(stx, {}, function (imgData) {
- var id = CIQ.uniqueID();
- var host = "https://share.chartiq.com";
- var url = host + "/upload/" + id;
- if (override) {
- if (override.host) host = override.host;
- if (override.path) url = host + override.path + "/" + id;
- }
- var startOffset = stx.getStartDateOffset();
- var metaData = {
- layout: stx.exportLayout(),
- drawings: stx.exportDrawings(),
- xOffset: startOffset,
- startDate: stx.chart.dataSegment[startOffset].Date,
- endDate: stx.chart.dataSegment[stx.chart.dataSegment.length - 1].Date,
- id: id,
- symbol: stx.chart.symbol
- };
- var payload = { id: id, image: imgData, config: metaData };
- CIQ.Share.uploadImage(imgData, url, payload, function (err, response) {
- if (err !== null) {
- CIQ.alert("error sharing chart: ", err);
- } else {
- cb(host + response);
- }
- });
- // end sample code to upload image to a server
- });
-};
-
-};
-
-
-let __js_standard_span_ = (_exports) => {
-
-/* global _CIQ, _timezoneJS, _SplinePlotter */
-
-var CIQ = typeof _CIQ !== "undefined" ? _CIQ : _exports.CIQ;
-
-/**
- * Used directly by {@link CIQ.ChartEngine#setRange} or indirectly by {@link CIQ.ChartEngine#loadChart}
- *
- * @typedef {Object} CIQ.ChartEngine~RangeParameters
- * @property {Date} [dtLeft] Date to set left side of the chart
- * @property {Date} [dtRight] Date to set right side of the chart
- * @property {number} [padding=0] Whitespace padding in pixels to apply to the right side of the chart after sizing for date range.
- * @property {CIQ.ChartEngine.Chart} [chart] Which chart, defaults to "chart"
- * @property {boolean} [goIntoFuture=false] set the right side of the chart to be in the future
- * @property {boolean} [goIntoPast=false] set the left side of the chart to be in the past
- * @property {CIQ.ChartEngine~PeriodicityParameters} [periodicity] Override a specific periodicity combination to use with the range
- * @property {number} [pixelPerBar] override automatic candle width calculations
- * @property {boolean} [dontSaveRangeToLayout=false] skip saving the range in the layout
- * @property {boolean} [forceLoad=false] a complete load (used by loadChart)
- */
-
-/**
- * Sets a chart to the requested date range.
- *
- * By default, the **Minimum Width** for a bar is `1px`. As such, there may be times when the requested data will not all fit on the screen, even though it is available.
- * See {@link CIQ.ChartEngine#minimumCandleWidth} for instructions on how to override the default to allow more data to display.
- *
- * When a quotefeed is attached to the chart (ver 04-2015 and up), and not enough data is available in masterData to render the requested range, setRange will request more from the feed.
- * Also, if no periodicity (params.periodicity) is supplied in the parameters, **it may override the current periodicity** and automatically choose the best periodicity to use for the requested range using the {@link CIQ.ChartEngine#dynamicRangePeriodicityMap} when {@link CIQ.ChartEngine#autoPickCandleWidth} is enabled,
- * or the use of the {@link CIQ.ChartEngine#staticRangePeriodicityMap} object when {@link CIQ.ChartEngine#autoPickCandleWidth} is **NOT** enabled.
- * So depending on your UI, **you may need to use the callback to refresh the periodicity displayed on your menu**.
- *
- * Therefore, if you choose to let setRange set the periodicity, you should **not** call setPeriodicity before or after calling this method.
- *
- * **For details on how this method can affect the way daily data is rolled up, see {@link CIQ.ChartEngine#createDataSet}**
- *
- * **If the chart is in `tick` periodicity, the periodicity will be automatically selected even if one was provided because in `tick` periodicity we have no way to know how many ticks to get to fulfill the requested range.**
- *
- * If there is no quotefeed attached (or using a version prior to 04-2015), then setRange will use whatever data is available in the masterData. So you must ensure you have preloaded enough to display the requested range.
- *
- * This function must be called after loadChart() creates a dataSet.
- *
- * **Layout preservation and the range**
- *
The selected range will be recorded in the chart {@link CIQ.ChartEngine#layout} when it is requested through {@link CIQ.ChartEngine#loadChart}, or when you call setRange directly.
- *
It is then used in {@link CIQ.ChartEngine#importLayout} and {@link CIQ.ChartEngine#loadChart} to reset that range, until a new range is selected.
- *
- * @param {CIQ.ChartEngine~RangeParameters} params Parameters for the request
- * @param {Date} [params.dtLeft] Date to set left side of chart. If no left date is specified then the right edge will be flushed, and the same interval and period will be kept causing the chart to simply scroll to the right date indicated.
**Must be in the exact same time-zone as the `masterdata`.** See {@link CIQ.ChartEngine#setTimeZone} and {@link CIQ.ChartEngine#convertToDataZone} for more details.
If the left date is not a valid market date/time, the next valid market period forward will be used.
- * @param {Date} [params.dtRight] Date to set right side of chart. Defaults to right now.
**Must be in the exact same time-zone as the `masterdata`.** See {@link CIQ.ChartEngine#setTimeZone} and {@link CIQ.ChartEngine#convertToDataZone} for more details.
If the right date is not a valid market date/time, the next valid market period backwards will be used.
- * @param {number} [params.padding] Whitespace padding in pixels to apply to right side of chart after sizing for date range. If not present then 0 will be used.
- * @param {CIQ.ChartEngine.Chart} [params.chart] Which chart, defaults to "chart"
- * @param {boolean} [params.goIntoFuture] If true then the right side of the chart will be set into the future if dtRight is greater than last tick. See {@link CIQ.ChartEngine#staticRange} if you wish to make this your default behavior.
- * @param {boolean} [params.goIntoPast] If true then the left side of the chart will be set into the past if dtLeft is less than first tick. See {@link CIQ.ChartEngine#staticRange} if you wish to make this your default behavior.
- * @param {CIQ.ChartEngine~PeriodicityParameters} [params.periodicity] Override a specific periodicity combination to use with the range. Only available if a quoteFeed is attached to the chart. Note: if the chart is in tick periodicity, the periodicity will be automatically selected even if one was provided because in tick periodicity we have no way to know how many ticks to get to fulfill the requested range. If used, all 3 elements of this object must be set.
- * @param {Number} params.periodicity.period Period as used by {@link CIQ.ChartEngine#setPeriodicity}
- * @param {string} params.periodicity.interval An interval as used by {@link CIQ.ChartEngine#setPeriodicity}
- * @param {string} params.periodicity.timeUnit A timeUnit as used by {@link CIQ.ChartEngine#setPeriodicity}
- * @param {Number} [params.pixelsPerBar] Optionally override this value so that the auto-periodicity selected chooses different sized candles.
- * @param {boolean} [params.dontSaveRangeToLayout] If true then the range won't be saved to the layout.
- * @param {boolean} [params.forceLoad] Forces a complete load (used by loadChart)
- * @param {Function} [cb] Callback method. Will be called with the error returned by the quotefeed, if any.
- * @memberOf CIQ.ChartEngine
- * @since
- * - 04-2015 Added `params.rangePeriodicityMap` and `params.periodicity` as well as automatic integration with {@link quotefeed}.
- * - 2016-05-10 Deprecated `params.rangePeriodicityMap` in favor of new automatic algorithm.
- * - m-2016-12-01 Restored logic to reference a periodicity map. Similar to previous `params.rangePeriodicityMap`. See {@link CIQ.ChartEngine#staticRangePeriodicityMap} for details.
- * - m-2016-12-01 Modified automatic periodicity algorithm. See {@link CIQ.ChartEngine#dynamicRangePeriodicityMap} and {@link CIQ.ChartEngine#autoPickCandleWidth} for details.
- * - 4.0.0 Now uses {@link CIQ.ChartEngine#needDifferentData} to determine if new data should be fetched.
- * - 4.0.0 No longer defaulting padding to current value of `preferences.whiteSpace`.
- * - 5.1.0 Added `params.dontSaveRangeToLayout`.
- * - 5.1.0 The selected range will be recorded in the chart {@link CIQ.ChartEngine#layout} when it is requested through {@link CIQ.ChartEngine#loadChart}, or when you call setRange directly.
- * - 5.2.0 `params.forceLoad` is now an option to force loading of new data.
- * @example
- *
- * stxx.setSpan({
- * multiplier: 5,
- * base: "day",
- * padding: 30,
- * // pre load a parameter for setRange
- * periodicity: {
- * period: 1,
- * interval: 5,
- * timeUnit: 'minute'
- * }
- * });
- *
- *
- * Just keep in mind that if passing `periodicity.period` , `periodicity.timeUnit` and `periodicity.interval` to be used in {@link CIQ.ChartEngine#setRange} , then **DO NOT** set `maintainPeriodicity`. Otherwise, the requested periodicity will be ignored.
- *
- * If a quotefeed is attached to the chart (ver 04-2015 and up), setSpan will attempt to gather more data from the feed (IF NEEDED) to fulfill the requested range AND **may override the periodicity** to provide the most optimal chart display.
- * So depending on your UI, **you may need to use the callback to refresh the periodicity displayed on your menu**.
- * Please see {@link CIQ.ChartEngine#setRange} and {@link CIQ.ChartEngine#displayAll} for complete details on how the periodicity is calculated.
- *
If there is no quotefeed attached (or using a version prior to 04-2015), then setStan will use whatever data is available in the masterData. So you must ensure you have preloaded enough to display the requested range.
- *
- * Calling {@link CIQ.ChartEngine#setPeriodicity} immediately after setting a span may cause all of the data to be re-fetched at a different periodicity than the one used by the requested span. Once you have set your initial periodicity for the chart, there is no need to manually change it when setting a new span unless you are using the `params.maintainPeriodicity` flag; in which case you want to call `setPeriodicity` **before** you set the span, so the setSpan call will use the pre-set periodicity.
- *
Setting a span to `params.multiplier:7` `params.base:'days'` or `params.multiplier:1` `params.base:'week'`, for example, is really the same thing; same span of time. If what you are trying to do is tell the chart how you want the raw data to be fetched, that is done with {@link CIQ.ChartEngine#setPeriodicity} or by letting setSpan figure it out as described above.
- *
Remember that by default, weekly and monthly data is calculated using daily raw ticks. If your feed returns data already rolled up in monthly or weekly ticks, you can override this behavior by setting `stxx.dontRoll` to `true` ( see {@link CIQ.ChartEngine#dontRoll} and the {@tutorial Periodicity} tutorial)
- *
- * This function must be called **after** loadChart() completes and creates a dataSet, or together with loadChart() by setting the proper parameter values.
- * If calling separately right after loadChart(), be sure to call it in the loadChart() callback!.
- * See example in this section and {@link CIQ.ChartEngine#loadChart} for more details and compatibility with your current version.
- *
- * Be aware that {@link CIQ.ChartEngine.Chart#allowScrollPast} and {@link CIQ.ChartEngine.Chart#allowScrollFuture} must be set to true if you wish to display "white space" in cases where the range requested is larger than the available data.
- * Especially when using "today" and the base.
- *
- * **Layout preservation and the span**
- *
If `maintainPeriodicity` is not set, the selected span will be recorded in the chart {@link CIQ.ChartEngine#layout} when it is requested through {@link CIQ.ChartEngine#loadChart}, or when you call setSpan directly.
- *
It is then used in {@link CIQ.ChartEngine#importLayout} and {@link CIQ.ChartEngine#loadChart} to reset that span, until a new periodicity is selected.
- *
- * **Note:** versions prior to '2015-05-01' must use the legacy arguments : setSpan(multiplier, base, padding, char,useMarketTZ,cb), and related example in this section.
- *
- * @param {CIQ.ChartEngine~SpanParameters} params Parameter for the function
- * @param {number} params.multiplier Number of base units to show. To show 3 weeks of data, for example, set this to 3 and `params.base` to 'week'.
- * @param {string} params.base The base span to show. "minute", "day", "week", "month", "year", "all", "ytd" or "today".
- *
Except when using "today", this base will be combined with the multiplier. Example 2 days, 4 months.
- *
**Spans are market hours sensitive**, so if you ask for 1 hour, for example, at the time the markets are close,
- * the span will find the last time the markets where open for the active symbol, and include the last market hour in the span.
- * It will also exclude days when the market is closed.
- * - If 'all' data is requested, {@link CIQ.ChartEngine#displayAll} is called first to ensure all quotefeed data for that particular instrument is loaded. Note that 'all' will display the data in `monthly` periodicity unless otherwise specified. Please note that "all" will attempt to load all of the data the quotefeed has available for that symbol. Use this span with caution.
- * - If 1 'day' is requested --on market days--the chart will start from the same time on the previous market day, which may be over a weekend. Example from 3:30 PM Friday to 3:30 PM Monday, if the market is closed Saturday and Sunday.
- * - If 1 'day' is requested --on weekends and holidays-- or if 2 or more days are requested, the chart will always start from market open of prior days.
- * - If 'today' is requested --during the market day -- the chart will display the current market day but, if {@link CIQ.ChartEngine.Chart#allowScrollFuture} is also enabled, extend the chart all the way to market close (as per market hours set in the active market definition - see {@link CIQ.Market})
- * - If 'today' is requested --before the market is open --the chart will display the previous market day.
- * - If 'today' is requested --after the current market day closes --the chart will display the current market day.
- * @param {boolean} [params.maintainPeriodicity] If set to true, it will maintain the current periodicity for the chart instead of trying to select the most optimal periodicity for the selected range. See {@link CIQ.ChartEngine#setRange} for details.
- *
**Note:** if the chart is in `tick` periodicity, the periodicity will be automatically selected even if it was requested to be maintained because in `tick` periodicity we have no way to know how many ticks to get to fulfill the requested range.
- * @param {number} [params.padding] Whitespace padding in pixels to apply to right side of chart after sizing for date range. If not set will default whitespace to 0.
- * @param {boolean} [params.forceLoad] Forces a complete load (used by loadChart)
- * @param {CIQ.ChartEngine.Chart} [params.chart] Which chart, defaults to "chart"
- * @param {Function} cb Optional callback
- * @memberOf CIQ.ChartEngine
- * @example
- * // this displays 5 days. It can be called anywhere including buttons on the UI
- * stxx.setSpan ({
- * multiplier: 5,
- * base: "day",
- * padding: 30
- * });
- * @example
- * // using embedded span requirements on a loadChart() call.
- * stxx.loadChart({symbol: newSymbol, other: 'stuff'}, {
- * span: {
- * base: 'day',
- * multiplier: 2
- * },
- * }, callbackFunction());
- * @example
- * // Calling setSpan in the loadChart() callback to ensure synchronicity.
- * stxx.loadChart({symbol: newSymbol, other: 'stuff'}, function() {
- * stxx.setSpan({
- * multiplier: 5,
- * base: "day",
- * padding: 30
- * });
- * });
- * @since
- * - 04-2015 Added "all", "today", "ytd" and automatic integration with {@link quotefeed}.
- * - 15-07-01 Changed `params.period` to `params.multiplier` for clarity.
- * - 15-07-01 Changed `params.interval` to `params.base` for clarity.
- * - 05-2016-10 Saves the set span in stxx.layout to be restored with the layout between sessions.
- * - 4.0.3 Saves all parameters of the requested span in stxx.layout to be restored with the layout between sessions. Previously only `multiplier` and `base` were saved.
- * - 5.0.0 When 1 'day' is requested data displayed will differ if current day is market day or the market is closed to ensure the span will have enough data.
- */
-CIQ.ChartEngine.prototype.setSpan = function (params, cb) {
- var period = arguments[0];
- var interval = arguments[1];
- var padding = arguments[2];
- var chart = arguments[3];
-
- if (typeof params == "object") {
- period = params.period
- ? params.period
- : params.multiplier
- ? params.multiplier
- : 1;
- interval = params.interval
- ? params.interval
- : params.base
- ? params.base
- : params.span
- ? params.span
- : params.period;
- padding = params.padding;
- chart = params.chart;
- } else {
- params = {
- period: period,
- interval: interval,
- padding: padding,
- chart: chart
- };
- cb = arguments[5];
- }
- // Do not force padding to 0 on setSpan
- //if(!params.padding) params.padding=0;
-
- if (!chart) chart = this.chart;
- var market = chart.market;
-
- interval = interval.toLowerCase();
- if (interval == "all") {
- params.dontSaveRangeToLayout = true;
- this.displayAll(params, cb);
- return;
- }
- var iter;
- var iterInterval = interval;
- var iterPeriod = 1;
- if (interval == "today") {
- iterInterval = "day";
- } else if (interval == "year") {
- iterInterval = "month";
- iterPeriod = 12;
- }
-
- var parms_copy = CIQ.shallowClone(params);
-
- var iter_parms = {
- begin: market.marketZoneNow(),
- interval: iterInterval,
- period: iterPeriod
- };
- var leftDT = iter_parms.begin;
-
- function zeroDT(dt) {
- dt.setHours(0);
- dt.setMinutes(0);
- dt.setSeconds(0);
- dt.setMilliseconds(0);
- return dt;
- }
- var isForex = CIQ.Market.Symbology.isForexSymbol(chart.symbol);
- function forexAdjust(dt, advance) {
- // The whole point of this function is to get a 1 day or today chart to start showing forex at 5pm the prior day instead of midnight,
- // without breaking the whole market class in the process.
- if (!isForex) return dt;
- var forexOffset = 7; // 7 hours from open to midnight
- if (advance) dt.setHours(dt.getHours() + forexOffset);
- // get it to the next day if it's after 5pm
- else {
- // it's assumed dt time is midnight if code gets in here
- dt.setHours(dt.getHours() - forexOffset); // start at 5pm prior trading day
- if (!market.isMarketDate(dt)) dt.setDate(dt.getDate() - 2); // For the weekend
- }
- return dt;
- }
- if (interval === "ytd") {
- leftDT = zeroDT(leftDT);
- leftDT.setMonth(0);
- leftDT.setDate(1);
- } else if (interval === "month") {
- leftDT = zeroDT(new Date());
- leftDT.setMonth(leftDT.getMonth() - period);
- } else if (interval === "year") {
- leftDT = zeroDT(new Date());
- leftDT.setFullYear(leftDT.getFullYear() - period);
- } else if (interval === "week") {
- leftDT = zeroDT(new Date());
- leftDT.setDate(leftDT.getDate() - period * 7);
- } else if (interval === "day" && period == 1 && market.isMarketDay()) {
- // Special case, 1 "day" --on market days-- will start from same time on previous market day
- // 1 day in weekends and holidays or 2 or more days will always start from market open of prior days (last else)
- var h = leftDT.getHours();
- var m = leftDT.getMinutes();
- var s = leftDT.getSeconds();
- var mm = leftDT.getMilliseconds();
- iter = market.newIterator(iter_parms);
- leftDT = iter.previous();
- leftDT.setHours(h, m, s, mm);
- leftDT = market._convertFromMarketTZ(leftDT);
- } else if (interval === "today") {
- iter_parms.begin = forexAdjust(leftDT, true);
- // forward and then back will land us on the most current valid market day
- iter = market.newIterator(iter_parms);
- if (
- market.isOpen() ||
- market.getPreviousOpen().getDate() == leftDT.getDate()
- ) {
- // if market opened, go ahead a day (we'll go back a day right after)
- iter.next();
- }
- leftDT = iter.previous();
- forexAdjust(leftDT);
-
- parms_copy.goIntoFuture = true;
- parms_copy.dtRight = new Date(+leftDT);
- parms_copy.dtRight.setDate(leftDT.getDate() + 1);
- parms_copy.dtRight = market._convertFromMarketTZ(parms_copy.dtRight);
-
- if (!isForex) {
- leftDT.setHours(iter.market.zopen_hour);
- leftDT.setMinutes(iter.market.zopen_minute);
- leftDT.setSeconds(0);
- }
-
- leftDT = market._convertFromMarketTZ(leftDT);
- } else {
- if (interval == "day") iter_parms.begin = forexAdjust(leftDT, true);
- iter = market.newIterator(iter_parms);
- if (period == 1) period++;
- leftDT = iter.previous(period - 1);
- if (interval == "day")
- leftDT = market._convertFromMarketTZ(forexAdjust(leftDT));
- }
- parms_copy.dtLeft = leftDT;
- if (parms_copy.maintainPeriodicity) {
- parms_copy.periodicity = {};
- parms_copy.periodicity.interval = this.layout.interval;
- parms_copy.periodicity.period = this.layout.periodicity;
- }
- chart.spanLock = false; // unlock left edge
- parms_copy.dontSaveRangeToLayout = true; // don't do certain things in setRange when being called from setSpan
- var self = this;
- this.setRange(parms_copy, function (err) {
- self.layout.setSpan = params;
- self.changeOccurred("layout");
-
- if (interval == "today") {
- chart.spanLock = true; // lock left edge of screen, in callback after we have fetched!
- }
- if (cb) cb(err);
- });
-};
-
-//@private
-// Foobarred function. Does not handle today or all properly. Assumes daily data. Not called from anywhere.
-CIQ.ChartEngine.prototype.getSpanCandleWidth = function (span) {
- if (!span || !span.base || !span.multiplier) return;
- var num = parseFloat(span.multiplier);
- var base = span.base;
- var now = new Date();
- var prev = new Date();
- if (base == "year") {
- prev.setFullYear(prev.getFullYear() - num);
- } else if (base == "month") {
- prev.setMonth(prev.getMonth() - num);
- } else if (base == "day") {
- prev.setDate(prev.getDate() - num);
- } else if (base == "week") {
- prev.setDate(prev.getDate() - 7 * num);
- } else if (base == "YTD") {
- prev.setMonth(0);
- prev.setDate(1);
- }
- var diff = (now.getTime() - prev.getTime()) / 1000 / 60 / 60 / 24;
- diff = (diff * 5) / 7;
- var candleWidth = this.chart.width / diff;
- return candleWidth;
-};
-
-/**
- * Sets a chart to display all data for a security.
- *
- * If no feed is attached, it will simply display all the data loaded in the present periodicity.
- *
If the chart is driven by a QuoteFeed and no periodicity is requested, it will default to 'monthly'.
- * It will then call QuoteDriver.loadAll() which makes multiple queries to ensure all data available from the quote feed is loaded.
- * Once all the data is loaded, the chart will be set to cover that range using {@link CIQ.ChartEngine#setRange}
- * @param {object} [params] Optional parameters in same format as {@link CIQ.ChartEngine#setSpan}.
- * @param {Function} [cb] Callback, is called when chart is displayed.
- * @since 04-2015
- * @memberOf CIQ.ChartEngine
- */
-CIQ.ChartEngine.prototype.displayAll = function (params, cb) {
- var { chart, layout } = this;
- if (params && params.chart) chart = params.chart;
- const self = this;
- function displayTheResults() {
- if (!chart.masterData || !chart.masterData.length) return;
- var p = CIQ.clone(params);
- p.dtLeft = chart.endPoints.begin.DT;
- p.dtRight = chart.endPoints.end.DT;
- // we already have the data, we just want to show it now. So make sure we maintain the periodicity so it won't fetch new one data
- p.periodicity = {};
- p.periodicity.interval = layout.interval;
- p.periodicity.period = layout.periodicity;
- p.periodicity.timeUnit = layout.timeUnit;
- self.setRange(p, function (err) {
- self.layout.setSpan = {
- base: params.base,
- multiplier: params.multiplier
- };
- self.changeOccurred("layout");
- for (var p in self.panels)
- self.calculateYAxisMargins(self.panels[p].yAxis);
- self.draw();
- if (cb) cb(err);
- });
- }
- function loadAllTheData(err) {
- if (!err) self.quoteDriver.loadAll(chart, displayTheResults);
- }
-
- // Case 1: no quoteFeed so display what we have
- if (!this.quoteDriver) {
- displayTheResults();
- return;
- }
-
- var periodicity = params.maintainPeriodicity
- ? {
- period: layout.periodicity,
- interval: layout.interval,
- timeUnit: layout.timeUnit
- }
- : { period: 1, interval: "month", timeUnit: null };
- periodicity = params.periodicity ? params.periodicity : periodicity;
-
- periodicity = CIQ.cleanPeriodicity(
- periodicity.period,
- periodicity.interval,
- periodicity.timeUnit
- );
-
- var needDifferentData = this.needDifferentData(periodicity);
-
- this.layout.periodicity = periodicity.period;
- this.layout.interval = periodicity.interval;
- this.layout.timeUnit = periodicity.timeUnit;
-
- // Case 2: new symbol or new periodicity
- if (params.forceLoad || needDifferentData) {
- this.clearCurrentMarketData(this.chart);
- this.quoteDriver.newChart(
- {
- noDraw: true,
- symbol: this.chart.symbol,
- symbolObject: this.chart.symbolObject,
- chart: this.chart,
- initializeChart: true,
- fetchMaximumBars: true
- },
- loadAllTheData
- );
- } else {
- // Case 3, the right interval is set but we don't have all the data
- if (chart.moreAvailable || !chart.upToDate) {
- loadAllTheData();
- } else {
- // Case 4, the right interval is set and we have all the data
- this.createDataSet(); // Just in case the interval changed from month to day or vice versa
- displayTheResults();
- }
- }
-};
-
-};
-
-
-let __js_standard_storage_ = (_exports) => {
-
-/* global _CIQ, _timezoneJS, _SplinePlotter */
-
-var CIQ = typeof _CIQ !== "undefined" ? _CIQ : _exports.CIQ;
-
-/**
- * QuoteFeed required if `params.noDataLoad` is set to `false`
- *
- * Imports a layout (panels, studies, candleWidth, etc) from a previous serialization. See {@link CIQ.ChartEngine#exportLayout}.
- *
- * There are 3 ways to use the this method:
- * 1. Preset the layout object in the chart instance, but do not load any data.
- * - This is usually used to restore an initial 'symbol independent' general layout (chart type and studies mainly) that will then take effect when `loadChart` is subsequently called.
- * - In this case, exportedLayout should be called using 'withSymbols=false' and the importLayout should have 'noDataLoad=true'.
- * 2. Load an entire new chart and its data, including primary symbol, additional series, studies, chart type, periodicity and range:
- * - In this case, you should not need call loadChart, setPeriodicity setSpan or setRange, addStudy, etc. since it is all restored from the previously exported layout and loaded using the attached quoteFeed.
- * - If you still wish to change periodicity, span or range, you must use the CB function to do so.
- * - In this case, exportedLayout should be called using 'withSymbols=true' and the importLayout should have 'noDataLoad=false' and 'managePeriodicity=true'.
- * 3. Reset layout on an already existing chart without changing the primary symbol or adding additional symbols:
- * - This is used when restoring a 'view' on an already existing chart from a previous `loadChart` call. The primary symbol remains the same, no additional series are added, but periodicity, range, studies and chart type are restored from the previously serialized view.
- * - In this case, exportedLayout should be called using 'withSymbols=false', and importLayout should have 'noDataLoad=false', managePeriodicity=true', and 'preserveTicksAndCandleWidth=true'.
- *
- * **Important Notes:**
- * - Please note that [studyOverlayEdit]{@link CIQ.ChartEngine~studyOverlayEditEventListener} and [studyPanelEdit]{@link CIQ.ChartEngine~studyPanelEditEventListener} event listeners must be set *before* you call {@link CIQ.ChartEngine#importLayout}.
- * Otherwise your imported studies will not have edit capabilities.
- *
- * - When symbols are loaded, this function will set the primary symbol (first on the serialized symbol list) with {@link CIQ.ChartEngine#loadChart}
- * and any overlayed symbol with {@link CIQ.ChartEngine#addSeries}. You must be using a QuoteFeed to use this workflow.
- *
- * - This method will not remove any currently loaded [series]{@link CIQ.ChartEngine#addSeries}.
- * If your restored layout should not include previously loaded series, you must first iterate trough the {@link CIQ.ChartEngine.Chart#series} object, and systematically call {@link CIQ.ChartEngine#removeSeries} on each entry.
- *
- * - When allowing this method to load data, do not call [addSeries]{@link CIQ.ChartEngine#addSeries}, [importDrawings]{@link CIQ.ChartEngine#importDrawings} or [loadChart]{@link CIQ.ChartEngine#loadChart}
- * in a way that will cause them to run simultaneously with this method, or the results of the layout load will be unpredictable.
- * Instead use this method's callback to ensure data is loaded in the right order.
- *
- * - Since spans and ranges require changes in data and periodicity,
- * they are only imported if params.managePeriodicity is set to true and params.noDataLoad is set to false.
- * If both range and span are present, range takes precedence.
- *
- * @param {object} config A serialized layout generated by {@link CIQ.ChartEngine#exportLayout}
- * @param {object} params Parameters to dictate layout behavior
- * @param {boolean} [params.noDataLoad=false] If true, then any automatic data loading from the quotefeed will be skipped, including setting periodicity, spans or ranges.
- * Data can only be loaded if a quote feed is attached to the chart.
- * @param {boolean} [params.managePeriodicity] If true then the periodicity will be set from the layout, otherwise periodicity will remain as currently set.
- * If the span/range was saved in the layout, it will be restored using the most optimal periodicity as determined by {@link CIQ.ChartEngine#setSpan}.
- * Periodicity can only be managed if a quote feed is attached to the chart.
- * Only applicable when noDataLoad=false.
- * See {@link CIQ.ChartEngine#setPeriodicity} for additional details
- * @param {boolean} [params.preserveTicksAndCandleWidth] If true then the current candleWidth (horizontal zoom) and scroll (assuming same periodicity) will be maintained and any spans or ranges present in the config will be ignored. Otherwise candle width and span/ranges will be taken from the config and restored.
- * @param {function} [params.cb] An optional callback function to be executed once the layout has been fully restored.
- * @param {function} [params.seriesCB] An optional callback function to be executed after each series is restored (to be aded to each {@link CIQ.ChartEngine#addSeries} call).
- * @memberof CIQ.ChartEngine
- * @since
- * - 05-2016-10 Symbols are also loaded if included on the serialization.
- * - 2016-06-21 `preserveTicksAndCandleWidth` now defaults to true.
- * - 3.0.0 Added `noDataLoad` parameter.
- * - 5.1.0 Will now also import extended hours settings.
- * - 5.1.0 Imports the range from layout if it is there to preserve between sessions.
- * - 5.2.0 spans and ranges are only executed if managePeriodicity is true and preserveTicksAndCandleWidth is false.
- */
-CIQ.ChartEngine.prototype.importLayout = function (config, params) {
- if (!config) {
- // if no config to restore, nothing to do.
- if (params.cb) params.cb();
- return;
- }
-
- var self = this;
- var importedPanels = [];
- function sortPanelAxes(panels) {
- function isdefined(i) {
- return !!i;
- }
- function sortSide(importedPanel, member) {
- if (!importedPanel[member] || !importedPanel[member].length) return;
- var panel = panels[importedPanel.name];
- if (!panel) return;
- var panelAxisArr = panel[member];
- var arr = new Array(panelAxisArr.length);
- for (var j = 0; j < panelAxisArr.length; j++) {
- var newPosition = importedPanel[member].indexOf(panelAxisArr[j].name);
- if (newPosition > -1) arr[newPosition] = panelAxisArr[j];
- else arr.push(panelAxisArr[j]);
- }
- if (arr.length) panel[member] = arr.filter(isdefined);
- }
- for (var i = 0; i < importedPanels.length; i++) {
- var importedPanel = importedPanels[i];
- sortSide(importedPanel, "yaxisLHS");
- sortSide(importedPanel, "yaxisRHS");
- }
- self.chart.yAxis = self.chart.panel.yAxis;
- }
-
- if (typeof params !== "object") {
- // backwards compatibility logic. This function used to accept three named arguments
- params = {
- managePeriodicity: arguments[1],
- preserveTicksAndCandleWidth: arguments[2]
- };
- }
- var layout = this.layout,
- originalLayout = CIQ.shallowClone(layout);
- var managePeriodicity = params.managePeriodicity,
- cb = params.cb,
- seriesCB = params.seriesCB,
- noDataLoad = params.noDataLoad;
- var preserveTicksAndCandleWidth = params.preserveTicksAndCandleWidth;
-
- var exportedDrawings = null;
- if (this.exportDrawings) {
- exportedDrawings = this.exportDrawings();
- this.abortDrawings();
- }
-
- this.currentlyImporting = true;
- // must remove studies before cleaning the overlays, or the remove function will be lost.
- for (var s in layout.studies) {
- var sd = layout.studies[s];
- CIQ.getFn("Studies.removeStudy")(this, sd);
- }
- this.overlays = {};
-
- // Keep a copy of the prior panels. We'll need these in order to transfer the holders
- var priorPanels = CIQ.shallowClone(this.panels);
- this.panels = {};
-
- // clone into view to prevent corrupting the original config object.
- var view = CIQ.clone(config);
- // copy all settings to the chart layout, but maintain the original periodicity,
- // which is handled later on depending on managePeriodicity and noDataLoad settings.
- layout.periodicity = originalLayout.periodicity;
- layout.interval = originalLayout.interval;
- layout.timeUnit = originalLayout.timeUnit;
- layout.setSpan = originalLayout.setSpan;
- layout.range = originalLayout.range;
-
- // must restore candleWidth before you draw any charts or series, including study charts. The config does not always provide the candleWidth
- if (preserveTicksAndCandleWidth) {
- layout.candleWidth = originalLayout.candleWidth;
- } else {
- if (!layout.candleWidth) layout.candleWidth = 8;
- }
- this.setCandleWidth(layout.candleWidth);
-
- // Flip chart upside down if flipped but set
- if (layout.flipped) this.flipChart(layout.flipped);
-
- var panels = view.panels; // make a copy of the panels
- var p;
- var panel;
- var yAxis;
- var sortByIndex = function (l, r) {
- return l.index < r.index ? -1 : 1;
- };
- for (p in panels) {
- if (!("index" in panels[p])) sortByIndex = null; // unable to sort
- panel = panels[p];
- panel.name = p;
- importedPanels.push(panel);
- }
- layout.panels = {}; // erase the panels
- var panelToSolo = null;
-
- if (importedPanels.length > 0) {
- // rebuild the panels
- if (sortByIndex) importedPanels.sort(sortByIndex);
- for (var i = 0; i < importedPanels.length; ++i) {
- panel = importedPanels[i];
- yAxis = panel.yAxis ? new CIQ.ChartEngine.YAxis(panel.yAxis) : null;
- this.stackPanel(
- panel.display,
- panel.name,
- panel.percent,
- panel.chartName,
- yAxis
- );
- if (panel.soloing) panelToSolo = this.panels[panel.name];
- }
- }
- if (CIQ.isEmpty(panels)) {
- this.stackPanel("chart", "chart", 1, "chart");
- }
- this.resizeCanvas();
-
- // Transfer the holders and DOM element references to panels that were retained when the config switched
- // Delete panels that weren't
- for (var panelName in priorPanels) {
- var oldPanel = priorPanels[panelName];
- var newPanel = this.panels[panelName];
- if (newPanel) {
- this.container.removeChild(newPanel.holder);
- if (oldPanel.handle) this.container.removeChild(oldPanel.handle);
- var copyFields = {
- holder: true,
- subholder: true,
- display: true,
- icons: true
- };
- for (var f in copyFields) {
- newPanel[f] = oldPanel[f];
- }
- this.configurePanelControls(newPanel);
- if (oldPanel.chart.panel == oldPanel) oldPanel.chart.panel = newPanel; // retain reference to the actual chart panel
- } else {
- this.privateDeletePanel(oldPanel);
- }
- }
- this.chart.panel = this.panels.chart; // make sure these are the same!
-
- sortPanelAxes(this.panels);
- CIQ.dataBindSafeAssignment(layout, CIQ.clone(view));
-
- var studies = CIQ.clone(layout.studies);
- delete layout.studies;
- for (var ss in studies) {
- var study = studies[ss];
- CIQ.getFn("Studies.addStudy")(
- this,
- study.type,
- study.inputs,
- study.outputs,
- study.parameters,
- study.panel
- );
- }
-
- if (this.extendedHours)
- this.extendedHours.prepare(layout.extended, layout.marketSessions);
-
- if (typeof layout.chartType == "undefined") layout.chartType = "line";
- this.setMainSeriesRenderer();
-
- if (panelToSolo) this.panelSolo(panelToSolo);
- this.adjustPanelPositions();
- sortPanelAxes(this.panels);
- this.storePanels();
-
- function postLayoutChange(err) {
- if (exportedDrawings) self.importDrawings(exportedDrawings);
- self.currentlyImporting = false;
- if (err) return;
- // Below is logic for re-adding the series used by studies.
- // We need this because we've removed the existing series when we removed studies.
- // When we readded studies we suspended the data loading since we were in the middle of importing
- // so here after turning off the importing flag, we readd these series to cause an initial load of its data
- // Note we need to reload the series data since it was cleaned out of masterData by removeStudy().
- var found;
- function cb() {
- self.createDataSet();
- sortPanelAxes(self.panels);
- self.calculateYAxisPositions();
- self.draw();
- }
- // For some series (such as those based on price relative studies) `addSeries()` will check whether there
- // already exist series with a matching symbol (to avoid refetching data). When we are removing and then
- // readding series, we need to remove them all before readding any. This is because not yet removed series
- // can cause readded studies to not get initialized properly.
- var series;
- var seriesToReadd = [];
- for (var s in self.chart.series) {
- if (!self.removeSeries) break;
- series = self.chart.series[s];
- if (series.parameters.bucket == "study") {
- found = true;
- self.removeSeries(series);
- seriesToReadd.push(series);
- }
- }
- for (var i = 0; i < seriesToReadd.length; i++) {
- series = seriesToReadd[i];
- self.addSeries(series.id, series.parameters, cb);
- }
- if (!found) self.draw();
- self.updateListeners("layout"); // tells listening objects that layout has changed
- self.changeOccurred("layout"); // dispatches to callbacklisteners
- }
-
- function cb2() {
- self.calculateYAxisPositions();
- sortPanelAxes(self.panels);
- if (seriesCB) seriesCB();
- }
- if (!noDataLoad) {
- // Now we execute the data loading functions.
- if (view.symbols && view.symbols.length) {
- // load symbols; primary and additional series. Also adjust ranges and periodicity at the same time
-
- var params2 = {
- chart: this.chart
- };
- if (
- !preserveTicksAndCandleWidth &&
- managePeriodicity &&
- view.range &&
- Object.keys(view.range).length
- ) {
- // spans and ranges are only executed if managePeriodicity is true and preserveTicksAndCandleWidth is false.
- params2.range = view.range;
- } else if (
- !preserveTicksAndCandleWidth &&
- managePeriodicity &&
- view.setSpan &&
- Object.keys(view.setSpan).length
- ) {
- // see above
- params2.span = view.setSpan;
- } else if (managePeriodicity && view.interval) {
- // otherwise, import periodicity if available
- params2.periodicity = {
- interval: view.interval,
- period: view.periodicity,
- timeUnit: view.timeUnit
- };
- } else {
- // otherwise, maintain prior periodicity
- params2.periodicity = {
- interval: originalLayout.interval,
- period: originalLayout.periodicity,
- timeUnit: originalLayout.timeUnit
- };
- }
-
- var symbolObject = view.symbols[0].symbolObject || view.symbols[0].symbol;
-
- this.loadChart(symbolObject, params2, function (err) {
- if (!err) {
- for (var smbl, i = 1; i < view.symbols.length; ++i) {
- if (!self.addSeries) break;
- smbl = view.symbols[i];
- if (!smbl.parameters) smbl.parameters = {};
- var parameters = CIQ.clone(smbl.parameters);
- if (this.panels[parameters.panel]) {
- self.addSeries(smbl.id, parameters, cb2);
- } else {
- console.warn(
- 'Warning: Series "' +
- smbl.id +
- '" could not be imported due to a missing corresponding panel "' +
- parameters.panel +
- '"'
- );
- }
- }
- if (view.chartScale) self.setChartScale(view.chartScale);
- }
- postLayoutChange(err);
- if (cb) cb.apply(null, arguments);
- });
- return;
- }
-
- // Otherwise, if only data ranges or periodicity are required, load them now
-
- if (managePeriodicity) {
- if (!preserveTicksAndCandleWidth && this.setRange) {
- // spans and ranges are only executed if managePeriodicity is true and preserveTicksAndCandleWidth is false.
- var range = view.range;
- if (range && Object.keys(range).length && this.chart.symbol) {
- this.setRange(range, function () {
- postLayoutChange();
- if (cb) cb();
- });
- return;
- } else if (
- view.setSpan &&
- Object.keys(view.setSpan).length &&
- this.chart.symbol
- ) {
- this.setSpan(view.setSpan, function () {
- postLayoutChange();
- if (cb) cb();
- });
- return;
- }
- }
-
- var interval = view.interval;
- var periodicity = view.periodicity;
- var timeUnit = view.timeUnit;
- if (isNaN(periodicity)) periodicity = 1;
- if (!interval) interval = "day";
- // this will get new data or roll up existing, createDataSet() and draw()
- this.setPeriodicity(
- { period: periodicity, interval: interval, timeUnit: timeUnit },
- function () {
- postLayoutChange();
- if (cb) cb();
- }
- );
- return;
- }
- }
-
- // if we got here, no data loading was requested.
- if (managePeriodicity) {
- layout.periodicity = view.periodicity;
- layout.interval = view.interval;
- layout.timeUnit = view.timeUnit;
- layout.setSpan = view.setSpan;
- }
-
- this.createDataSet();
- if (!preserveTicksAndCandleWidth) this.home();
- postLayoutChange();
- if (cb) cb();
-};
-
-/**
- * Exports the current layout into a serialized form. The returned object can be passed into {@link CIQ.ChartEngine#importLayout} to restore the layout at a future time.
- *
- * This method will also save any programmatically activated [range]{@link CIQ.ChartEngine#setRange} or [span]{@link CIQ.ChartEngine#setSpan} setting that is still active.
- *
- * > **Note:** A set range or span that is manually modified by a user when zooming, panning, or changing periodicity will be nullified.
- * > So, if you wish to always record the current range of a chart for future restoration, you must use the following process:
- *
- * > 1- Add the following injection to save the range on every draw operation:
- * > ```
- * > stxx.append("draw", function() {
- * > console.log('recording range');
- * > delete stxx.layout.setSpan;
- * > stxx.layout.range={padding: stxx.preferences.whitespace,
- * > dtLeft: stxx.chart.dataSegment[0].DT,
- * > dtRight: stxx.chart.dataSegment[stxx.chart.dataSegment.length - 1].DT,
- * > periodicity: {
- * > period: stxx.layout.periodicity,
- * > interval: stxx.layout.interval,
- * > timeUnit: stxx.layout.timeUnit
- * > }
- * > }
- * > saveLayout({stx:stxx});
- * > });
- * > ```
- *
- * > 2- Make sure you call [importLayout]{@link CIQ.ChartEngine#importLayout} with params `preserveTicksAndCandleWidth` set to `false`
- *
- * > More on injections here: {@tutorial Using the Injection API}
- *
- * @param {boolean} withSymbols If `true`, include the chart's current primary symbol and any secondary symbols from any {@link CIQ.ChartEngine#addSeries} operation, if using a quote feed. Studies will be excluded from this object. The resulting list will be in the `symbols` element of the serialized object.
- * @return {object} The serialized form of the layout.
- * @memberof CIQ.ChartEngine
- * @since
- * - 05-2016-10 Added the `withSymbols` parameter.
- * - 5.0.0 `obj.symbols` is explicitly removed from the serialization when `withSymbols` is not true.
- */
-CIQ.ChartEngine.prototype.exportLayout = function (withSymbols) {
- var obj = {};
- // First clone all the fields, these describe the layout
- for (var field in this.layout) {
- if (field != "studies" && field != "panels" && field != "drawing") {
- obj[field] = CIQ.clone(this.layout[field]);
- } else if (field == "studies") {
- obj.studies = {};
- } else if (field == "panels") {
- obj.panels = {};
- }
- }
-
- function serializeAxisNames(axisArr) {
- var nameArr = [];
- for (var i = 0; i < axisArr.length; i++) {
- nameArr.push(axisArr[i].name);
- }
- return nameArr;
- }
-
- // Serialize the panels
- var i = 0;
- for (var panelName in this.panels) {
- var p = this.panels[panelName];
- if (p.exportable === false) continue;
- var panel = (obj.panels[panelName] = {});
- panel.percent = p.percent;
- panel.display = p.display;
- panel.chartName = p.chart.name;
- panel.soloing = p.soloing;
- panel.index = i++;
- panel.yAxis = { name: p.yAxis.name, position: p.yAxis.position };
- if (p.yaxisLHS) panel.yaxisLHS = serializeAxisNames(p.yaxisLHS);
- if (p.yaxisRHS) panel.yaxisRHS = serializeAxisNames(p.yaxisRHS);
- }
-
- // Serialize the studies
- for (var studyName in this.layout.studies) {
- var study = (obj.studies[studyName] = {});
- var s = this.layout.studies[studyName];
- study.type = s.type;
- study.inputs = CIQ.clone(s.inputs);
- study.outputs = CIQ.clone(s.outputs);
- study.panel = s.panel;
- study.parameters = CIQ.clone(s.parameters);
- }
-
- if (withSymbols) {
- obj.symbols = this.getSymbols({
- "include-parameters": true,
- "exclude-studies": true,
- "exclude-generated": true
- });
- } else {
- delete obj.symbols;
- }
-
- return obj;
-};
-
-/**
- * Imports a users preferences from a saved location and uses them in the ChartEngine
- * To save preferences see {@link CIQ.ChartEngine#exportPreferences}
- * @param {object} preferences An object of {@link CIQ.ChartEngine#preferences}
- * @memberof CIQ.ChartEngine
- * @since 4.0.0
- */
-CIQ.ChartEngine.prototype.importPreferences = function (preferences) {
- CIQ.extend(this.preferences, preferences);
- if (preferences.timeZone)
- this.setTimeZone(this.dataZone, preferences.timeZone);
- if (preferences.language && CIQ.I18N) {
- CIQ.I18N.localize(this, preferences.language);
- }
- this.changeOccurred("preferences");
-};
-
-/**
- * Exports the {@link CIQ.ChartEngine#preferences} for external storage.
- * Can then be imported again after being parsed with {@link CIQ.ChartEngine#importPreferences}
- * @memberof CIQ.ChartEngine
- * @returns {CIQ.ChartEngine#preferences}
- * @since 4.0.0
- */
-CIQ.ChartEngine.prototype.exportPreferences = function () {
- return this.preferences;
-};
-
-};
-
-
-let __js_standard_studies_ = (_exports) => {
-
-/* global _CIQ, _timezoneJS, _SplinePlotter */
-
-var CIQ = typeof _CIQ !== "undefined" ? _CIQ : _exports.CIQ;
-var timezoneJS =
- typeof _timezoneJS !== "undefined" ? _timezoneJS : _exports.timezoneJS;
-
-if (CIQ.ChartEngine) {
- /**
- * INJECTABLE
- *
- * This function is called when a highlighted study overlay is right clicked. If the overlay has an edit function (as many studies do), it will be called. Otherwise it will remove the overlay
- * @param {string} name The name (id) of the overlay
- * @param {boolean} [forceEdit] If true then force edit menu
- * @memberof CIQ.ChartEngine.AdvancedInjectable#
- * @alias rightClickOverlay
- */
- CIQ.ChartEngine.prototype.rightClickOverlay = function (name, forceEdit) {
- if (this.runPrepend("rightClickOverlay", arguments)) return;
- var sd = this.overlays[name];
- if (sd.editFunction) {
- sd.editFunction(forceEdit);
- } else {
- this.removeOverlay(name);
- }
- this.runAppend("rightClickOverlay", arguments);
- };
-
- /**
- * INJECTABLE
- *
- * Registers an activated overlay study with the chart.
- *
- * This is the recommended method for registering an overlay study, rather than directly manipulating the [stxx.overlays]{@link CIQ.ChartEngine#overlays} object.
- * @param {CIQ.Studies.StudyDescriptor} sd The study object
- * @memberof CIQ.ChartEngine.AdvancedInjectable#
- * @alias addOverlay
- * @since 5.2.0
- */
- CIQ.ChartEngine.prototype.addOverlay = function (sd) {
- if (this.runPrepend("addOverlay", arguments)) return;
- this.overlays[sd.name] = sd;
- this.runAppend("addOverlay", arguments);
- };
-
- /**
- * INJECTABLE
- *
- * Removes an overlay (and the associated study)
- * @param {string} name The name (id) of the overlay
- * @memberof CIQ.ChartEngine.AdvancedInjectable#
- * @alias removeOverlay
- */
- CIQ.ChartEngine.prototype.removeOverlay = function (name) {
- if (this.runPrepend("removeOverlay", arguments)) return;
- var mySD = this.overlays[name];
- for (var o in this.overlays) {
- var sd = this.overlays[o];
- var fieldInputs = ["Field"];
- if (CIQ.Studies) fieldInputs = CIQ.Studies.getFieldInputs(sd);
- for (var f = 0; f < fieldInputs.length; f++) {
- // Study sd is reliant on an output from the about-to-be-deleted overlay
- if (mySD.outputMap[sd.inputs[fieldInputs[f]]]) {
- // Yucky, we should move to explicit parent nodes
- this.removeOverlay(sd.name);
- }
- }
- }
-
- if (mySD) {
- this.cleanupRemovedStudy(mySD);
- var panel = this.panels[mySD.panel];
- delete this.overlays[name];
- this.checkForEmptyPanel(mySD.panel);
- }
-
- if (!this.currentlyImporting) {
- // silent mode while importing
- this.displaySticky();
- this.createDataSet();
- this.changeOccurred("layout");
- }
- this.resetDynamicYAxis();
- this.runAppend("removeOverlay", arguments);
- };
-
- /**
- * Cleans up a removed study. called by {@link CIQ.ChartEngine#privateDeletePanel} or {@link CIQ.ChartEngine#removeOverlay}
- * Calls removeFN, and plugins associated with study.
- * Finally, removes study from layout.
- * @param {CIQ.ChartEngine} stx A chart object
- * @param {object} sd A study descriptor
- * @memberof CIQ.ChartEngine
- * @private
- * @since 2015-11-1
- */
- CIQ.ChartEngine.prototype.cleanupRemovedStudy = function (sd) {
- if (!sd) return;
- if (sd.study.removeFN) sd.study.removeFN(this, sd);
- // delete any plugins associated with this study
- for (var p in this.plugins) {
- if (p.indexOf("{" + sd.id + "}") > -1) delete this.plugins[p];
- }
- if (this.layout.studies) delete this.layout.studies[sd.name];
- delete this.overlays[sd.name];
- if (CIQ.Studies) CIQ.Studies.removeStudySymbols(sd, this);
- if (this.quoteDriver) this.quoteDriver.updateSubscriptions();
- };
-}
-
-/**
- * Namespace for functionality related to studies (aka indicators).
- *
- * See {@tutorial Using and Customizing Studies} for additional details and a general overview about studies.
- * @namespace
- * @name CIQ.Studies
- */
-CIQ.Studies = CIQ.Studies || function () {};
-
-/**
- * Constants for when no inputs or outputs specified in studies.
- * Values can be changed but do not change keys.
- * @memberof CIQ.Studies
- */
-CIQ.Studies.DEFAULT_INPUTS = { Period: 14 };
-CIQ.Studies.DEFAULT_OUTPUTS = { Result: "auto" };
-
-CIQ.Studies.sortForProcessing = (stx) => {
- function setIndependentStudies(list, arr) {
- list.forEach((study) => {
- if (arr.indexOf(study) == -1) {
- let dependents = study.getDependents(stx);
- if (dependents.length) setIndependentStudies(dependents, arr);
- arr.unshift(study);
- }
- });
- }
- let sortArray = [];
- const studies = stx.layout.studies;
- if (studies) {
- setIndependentStudies(Object.values(studies), sortArray);
- }
- return sortArray;
-};
-
-/**
- * Creates a study descriptor which contains all of the information necessary to handle a study. Also
- * provides convenience methods to extract information from it.
- *
- * Do not call directly or try to manually create your own study descriptor, but rather always use the one returned by {@link CIQ.Studies.addStudy}
- *
- * @param {string} name The name of the study. This should be unique to the chart. For instance if there are two RSI panels then they should be of different periods and named accordingly. Usually this is determined automatically by the library.
- * @param {string} type The type of study, which can be used as a look up in the StudyLibrary
- * @param {string} panel The name of the panel that contains the study
- * @param {object} inputs Names and values of input fields
- * @param {object} outputs Names and values (colors) of outputs
- * @param {object} parameters Additional parameters that are unique to the particular study
- * @constructor
- * @name CIQ.Studies.StudyDescriptor
- */
-CIQ.Studies.StudyDescriptor = function (
- name,
- type,
- panel,
- inputs,
- outputs,
- parameters
-) {
- /**
- * @property {string} name The study's ID. Includes ZWNJ characters.
- * **Please note:** To facilitate study name translations, study names use zero-width non-joiner (unprintable) characters to delimit the general study name from the specific study parameters.
- * Example: "\u200c"+"Aroon"+"\u200c"+" (14)".
- * At translation time, the library will split the text into pieces using the ZWNJ characters, parentheses and commas to just translate the required part of a study name.
- * For more information on ZWNJ characters see: [Zero-width_non-joiner](https://en.wikipedia.org/wiki/Zero-width_non-joiner).
- * Please be aware of these ZWNJ characters, which will now be present in all study names and corresponding panel names; including the `layout.studies` study keys.
- * Affected fields in the study descriptors could be `id `, `display`, `name` and `panel`.
- *
To prevent issues, always use the names returned in the **study descriptor**. This will ensure compatibility between versions.
- * >Example:
- * >
Correct reference:
- * >
`stxx.layout.studies["\u200c"+"Aroon"+"\u200c"+" (14)"];`
- * >
Incorrect reference:
- * >
`stxx.layout.studies["Aroon (14)"];`
- */
- this.name = name;
- /**
- * @property {string} type The study type.
- */
- this.type = type;
- /**
- * @property {string} panel ID of the panel element to which the study is attached.
- */
- this.panel = panel;
- /**
- * @property {object} inputs Keys for each possible study input with descriptors for the set and default values.
- */
- this.inputs = inputs;
- /**
- * @property {object} outputs Keys for each possible study output with its corresponding rendering color.
- */
- this.outputs = outputs;
- /**
- * @property {object} parameters Keys for each of the study's possible plot parameters.
- */
- this.parameters = parameters; // Optional parameters, i.e. zones.
- /**
- * @property {object} outputMap Mapping between a unique study field name in the dataSet/datSegment and its corresponding general `outputs` name/color, as set in the study library entry.
- * This mapping is automatically created and present on all study descriptors, and used by all default study functions to ensure data generated by a calculation function can be found by the display function.
- * Example:
- * ```
- * // Map for an Alligator study with inputs of:
- * // -Jaw Period:13
- * // -Jaw Offset:8
- * // -Teeth Period:8
- * // -Teeth Offset:5
- * // -Lips Period:5
- * // -Lips Offset:3
- * // -Show Fractals:false
- *
- * {
- * "Jaw Alligator (13,8,8,5,5,3,n)": "Jaw",
- * "Teeth Alligator (13,8,8,5,5,3,n)": "Teeth",
- * "Lips Alligator (13,8,8,5,5,3,n)": "Lips"
- * }
- * ```
- */
- this.outputMap = {}; // Maps dataSet label to outputs label "RSI (14)" : "RSI", for the purpose of figuring color.
- /**
- * @property {number} min The minimum data point.
- */
- this.min = null;
- /**
- * @property {number} max The maximum data point.
- */
- this.max = null;
- this.startFrom = 0;
- this.subField = "Close"; // In case study is off a series
- var libraryEntry = CIQ.Studies.studyLibrary[type];
- if (!libraryEntry) {
- libraryEntry = {};
- if (
- panel == "chart" ||
- (!panel && parameters && parameters.chartName == "chart")
- )
- this.overlay = true;
- }
- if (typeof libraryEntry.inputs == "undefined")
- libraryEntry.inputs = CIQ.clone(CIQ.Studies.DEFAULT_INPUTS);
- if (typeof libraryEntry.outputs == "undefined")
- libraryEntry.outputs = CIQ.clone(CIQ.Studies.DEFAULT_OUTPUTS);
-
- this.study = libraryEntry;
- this.libraryEntry = libraryEntry; // deprecated, backwards compatibility
-};
-
-/**
- * Returns the y-axis used by the study
- * @param {CIQ.ChartEngine} stx CIQ.ChartEngine
- * @memberof CIQ.Studies.StudyDescriptor
- * @return {CIQ.ChartEngine.YAxis} Y axis
- * @since 7.1.0
- */
-CIQ.Studies.StudyDescriptor.prototype.getYAxis = function (stx) {
- var yAxis = this.yAxis;
- var specifiedYAxis;
- if (this.parameters) {
- specifiedYAxis = this.parameters.yaxisDisplayValue;
- }
- if (!yAxis) {
- var testPanel = stx.panels[this.panel];
- if (testPanel) {
- yAxis =
- stx.getYAxisByName(testPanel, specifiedYAxis) ||
- stx.getYAxisByName(testPanel, this.name) ||
- testPanel.yAxis;
- }
- }
- if (!yAxis)
- yAxis =
- stx.getYAxisByName(stx.chart.panel, specifiedYAxis) ||
- stx.chart.panel.yAxis;
- return yAxis;
-};
-
-/**
- * Returns the context to use for drawing the study
- * @param {CIQ.ChartEngine} stx A chart object
- * @return {object} An HTML canvas context
- * @memberof CIQ.Studies.StudyDescriptor
- * @since 7.1.0
- */
-CIQ.Studies.StudyDescriptor.prototype.getContext = function (stx) {
- // If the study is draggable it will be placed on the tempCanvas and so that canvas's context will be returned.
- //if(this.highlight && stx.highlightedDraggable) return stx.chart.tempCanvas.context;
- return stx.chart.context;
-};
-
-/**
- * Returns an array of all studies which depend on a given study.
- * A dependent study is one which uses an output of another study as input.
- * @param {CIQ.ChartEngine} stx A chart object
- * @param {boolean} [followsPanel] If true, will only return those studies which are not assigned to an explicit panel
- * @return {array} Array of dependent studies
- * @memberof CIQ.Studies.StudyDescriptor
- * @since 7.1.0
- */
-CIQ.Studies.StudyDescriptor.prototype.getDependents = function (
- stx,
- followsPanel
-) {
- var dependents = [];
- for (var s in stx.layout.studies) {
- var dependent = stx.layout.studies[s];
- if (dependent == this) continue;
- var fieldInputs = CIQ.Studies.getFieldInputs(dependent);
- for (var f = 0; f < fieldInputs.length; f++) {
- if (dependent.inputs[fieldInputs[f]].includes(this.name)) {
- if (
- followsPanel &&
- dependent.parameters &&
- dependent.parameters.panelName
- )
- continue;
- dependents.push(dependent);
- dependents = dependents.concat(
- dependent.getDependents(stx, followsPanel)
- );
- break;
- }
- }
- }
- return dependents;
-};
-
-/**
- * Determines whether the study can be dragged to another axis or panel.
- *
- * @param {CIQ.ChartEngine} stx A chart object.
- * @return {boolean} true if not allowed to drag.
- * @memberof CIQ.Studies.StudyDescriptor
- * @since 7.3.0
- */
-CIQ.Studies.StudyDescriptor.prototype.undraggable = function (stx) {
- var attr = this.study.attributes;
- if (attr) {
- if (attr.panelName && attr.panelName.hidden) return true;
- if (attr.yaxisDisplayValue && attr.yaxisDisplayValue.hidden) return true;
- }
- return false;
-};
-
-/**
- * Adds extra ticks to the end of the scrubbed array, to be added later to the dataSet.
- *
- * This function can be used to add extra ticks, like offsets into the future, to the dataSet to be plotted ahead of the current bar.
- * If a DT is not supplied, one will be calculate for each tick in the array.
- *
- * Remember to call this outside of any loop that iterates through the quotes array, or you will create a never-ending loop, since this increases the array size.
- *
- * @param {CIQ.ChartEngine} stx A chart engine instance
- * @param {array} ticks The array of ticks to add. Each tick is an object containing whatever data to add.
- * @example
- * var futureTicks=[];
- * for(i++;i
ie: if custom name is 'SAMPLE', the unique name returned would resemble "SAMPLE(paam1,param2,param3,...)-X".
- * @return {string} A unique name for the study
- * @memberof CIQ.Studies
- * @since 5.1.1 Added `customName` argument; if supplied, use it to form the full study name. Otherwise `studyName` will be used.
- */
-CIQ.Studies.generateID = function (
- stx,
- studyName,
- inputs,
- replaceID,
- customName
-) {
- var libraryEntry = CIQ.Studies.studyLibrary[studyName];
- var translationPiece = "\u200c" + (customName || studyName) + "\u200c"; // zero-width non-joiner (unprintable) to delimit translatable phrase
- var id = translationPiece;
- if (libraryEntry) {
- // only one instance can exist at a time if custom removal, so return study name
- if (libraryEntry.customRemoval) return id;
- }
- if (!CIQ.isEmpty(inputs)) {
- var first = true;
- for (var field in inputs) {
- // some values do not merit being in the study name
- if (["id", "display", "Shading", "Anchor Selector"].includes(field)) {
- continue;
- }
-
- var val = inputs[field];
- if (val == "field") continue; // skip default, usually means "Close"
- val = val.toString();
- if (CIQ.Studies.prettify[val] !== undefined)
- val = CIQ.Studies.prettify[val];
- if (first) {
- first = false;
- id += " (";
- } else {
- if (val) id += ",";
- }
- id += val;
- }
- if (!first) id += ")";
- }
-
- //this tests if replaceID is just a warted version of id, in that case keep the old id
- if (replaceID && replaceID.indexOf(id) === 0) return replaceID;
-
- // If the id already exists then we'll wart it by adding -N
- if (stx.layout.studies && stx.layout.studies[id]) {
- for (var i = 2; i < 50; i++) {
- var warted = id + "-" + i;
- if (!stx.layout.studies[warted]) {
- id = warted;
- break;
- }
- }
- }
- return id;
-};
-
-/**
- * A helper class for adding studies to charts, modifying studies, and creating study edit dialog
- * boxes.
- *
- * Study DialogHelpers are created from
- * [study definitions](tutorial-Using%20and%20Customizing%20Studies%20-%20Study%20objects.html#understanding_the_study_definition)
- * or
- * [study descriptors](tutorial-Using%20and%20Customizing%20Studies%20-%20Study%20objects.html#understanding_the_study_descriptor_object)
- * (see the examples below).
- *
- * A DialogHelper contains the inputs, outputs, and parameters of a study. Inputs configure the
- * study. Outputs style the lines and filled areas of the study. Parameters set chart‑related
- * aspects of the study, such as the panel that contains the study or whether the study is an
- * underlay.
- *
- * For example, a DialogHelper for the Anchored VWAP study contains the following data:
- * ```
- * inputs: Array(8)
- * 0: {name: "Field", heading: "Field", value: "Close", defaultInput: "Close", type: "select", …}
- * 1: {name: "Anchor Date", heading: "Anchor Date", value: "", defaultInput: "", type: "date"}
- * 2: {name: "Anchor Time", heading: "Anchor Time", value: "", defaultInput: "", type: "time"}
- * 3: {name: "Display 1 Standard Deviation (1σ)", heading: "Display 1 Standard Deviation (1σ)", value: false,
- * defaultInput: false, type: "checkbox"}
- * 4: {name: "Display 2 Standard Deviation (2σ)", heading: "Display 2 Standard Deviation (2σ)", value: false,
- * defaultInput: false, type: "checkbox"}
- * 5: {name: "Display 3 Standard Deviation (3σ)", heading: "Display 3 Standard Deviation (3σ)", value: false,
- * defaultInput: false, type: "checkbox"}
- * 6: {name: "Shading", heading: "Shading", value: false, defaultInput: false, type: "checkbox"}
- * 7: {name: "Anchor Selector", heading: "Anchor Selector", value: true, defaultInput: true, type: "checkbox"}
- * outputs: Array(4)
- * 0: {name: "VWAP", heading: "VWAP", defaultOutput: "#FF0000", color: "#FF0000"}
- * 1: {name: "1 Standard Deviation (1σ)", heading: "1 Standard Deviation (1σ)", defaultOutput: "#e1e1e1", color: "#e1e1e1"}
- * 2: {name: "2 Standard Deviation (2σ)", heading: "2 Standard Deviation (2σ)", defaultOutput: "#85c99e", color: "#85c99e"}
- * 3: {name: "3 Standard Deviation (3σ)", heading: "3 Standard Deviation (3σ)", defaultOutput: "#fff69e", color: "#fff69e"}
- * parameters: Array(4)
- * 0: {name: "panelName", heading: "Panel", defaultValue: "Auto", value: "Auto", options: {…}, …}
- * 1: {name: "underlay", heading: "Show as Underlay", defaultValue: false, value: undefined, type: "checkbox"}
- * 2: {name: "yaxisDisplay", heading: "Y-Axis", defaultValue: "default", value: "shared", options: {…}, …}
- * 3: {name: "flipped", heading: "Invert Y-Axis", defaultValue: false, value: false, type: "checkbox"}
- * ```
- *
- * which corresponds to the fields of the study edit dialog box:
- *
- *
- *
- * DialogHelpers also contain `attributes` which specify the formatting of dialog box input
- * fields. For example, the DialogHelper for the Anchored VWAP study contains the following:
- * ```
- * attributes:
- * Anchor Date: {placeholder: "yyyy-mm-dd"}
- * Anchor Time: {placeholder: "hh:mm:ss", step: 1}
- * flippedEnabled: {hidden: true}
- * ```
- *
- * The `placeholder` property (in addition to its normal HTML function of providing placeholder
- * text) determines the input type of date and time fields. If the property value is "yyyy-mm-dd"
- * for a date field, the field in the edit dialog box is a date input type instead of a string
- * input. If the value is "hh:mm:ss" for a time field, the field is a time input type instead of a
- * string. If the `hidden` property of a field is set to true, the field is excluded from the
- * study edit dialog box.
- *
- * In the Anchored VWAP edit dialog box (see above), the Anchor Date field is formatted as a date
- * input type; Anchor Time, as a time input type. The Invert Y-Axis check box (the "flipped"
- * parameter) is hidden.
- *
- * **Note:** Actual date/time displays are browser dependent. The time is displayed in the
- * `displayZone` time zone. Time values are converted to the `dataZone` time zone before being
- * used internally so they always match the time zone of `masterData`. See
- * {@link CIQ.ChartEngine#setTimeZone}.
- *
- * For more information on DialogHelpers, see the
- * {@tutorial Using and Customizing Studies - Advanced} tutorial.
- *
- * @see {@link CIQ.Studies.addStudy} to add a study to the chart using the inputs, outputs, and
- * parameters of a DialogHelper.
- * @see {@link CIQ.Studies.DialogHelper#updateStudy} to add or modify a study.
- * @see {@link CIQ.UI.StudyEdit} to create a study edit dialog box using a DialogHelper.
- *
- * @param {object} params Constructor parameters.
- * @param {string} [params.name] The name of a study. The DialogHelper is created from the study's
- * definition. Must match a name specified in the
- * [study library]{@link CIQ.Studies.studyLibrary}. Ignored if `params.sd` is provided.
- * @param {CIQ.Studies.StudyDescriptor} [params.sd] A study descriptor from which the
- * DialogHelper is created. Takes precedence over `params.name`.
- * @param {boolean} [params.axisSelect] If true, the parameters property of the DialogHelper
- * includes options for positioning the study y-axis, color settings for the y-axis, and the
- * Invert Y‑Axis option.
- * @param {boolean} [params.panelSelect] If true, the parameters property of the DialogHelper
- * includes the Show as Underlay option and a list of panels in which the study can be
- * placed.
- * @param {CIQ.ChartEngine} params.stx The chart object associated with the DialogHelper.
- *
- * @name CIQ.Studies.DialogHelper
- * @constructor
- * @since
- * - 6.3.0 Added parameters `axisSelect` and `panelSelect`. If a placeholder attribute of
- * `yyyy-mm-dd` or `hh:mm:ss` is set on an input field, the dialog displays a date or time
- * input type instead of a string input type.
- * - 7.1.0 It is expected that the study dialog's parameters section is refreshed whenever the
- * DialogHelper changes. The "signal" member should be observed to see if it has flipped.
- * - 8.2.0 Attribute property values in the study definition can now be functions. See the
- * [Input Validation](tutorial-Using%20and%20Customizing%20Studies%20-%20Advanced.html#InputValidation)
- * section of the {@tutorial Using and Customizing Studies - Advanced} tutorial.
- *
- * @example
- * The panel name needs to change and the input "Field".
- * @private
- * @param {CIQ.ChartEngine} stx The stx instance
- * @param {CIQ.Studies.StudyDescriptor} masterStudy The old study whose dependents are to be rejiggered
- * @param {string} newID The new ID for the underlying study
- * @memberof CIQ.Studies
- * @since
- * - 5.2.0 Removed `panelID` argument.
- * - 7.0.0 Also fixes drawings.
- * - 7.1.0 Changed second argument.
- */
-CIQ.Studies.rejiggerDerivedStudies = function (stx, masterStudy, newID) {
- var replaceID = masterStudy.name;
- var oldPanel = masterStudy.panel;
- var dependents = masterStudy.getDependents(stx);
- for (var s = 0; s < dependents.length; s++) {
- var st = dependents[s];
- var inputs = CIQ.clone(st.inputs);
- var oldId = inputs.id;
- if (!oldId) continue;
- var stNeedsReplacement = false;
- var fieldInputs = CIQ.Studies.getFieldInputs(st);
- for (var f = 0; f < fieldInputs.length; f++) {
- inputs[fieldInputs[f]] = inputs[fieldInputs[f]].replace(replaceID, newID);
- }
- var sd = CIQ.Studies.replaceStudy(
- stx,
- oldId,
- st.type,
- inputs,
- st.outputs,
- CIQ.extend(st.parameters, { rejiggering: true }),
- null,
- st.study
- );
- delete sd.parameters.rejiggering;
- }
-};
-
-/**
- * Removes any series that the study is referencing.
- *
- * @param {object} sd Study descriptor.
- * @param {CIQ.ChartEngine} stx The chart engine.
- *
- * @memberof CIQ.Studies
- * @since
- * - 3.0.0
- * - 3.0.7 Changed `name` argument to take a study descriptor.
- * - 3.0.7 Added required `stx` argument.
- */
-CIQ.Studies.removeStudySymbols = function (sd, stx) {
- if (sd.series) {
- for (var s in sd.series) {
- stx.deleteSeries(sd.series[s], null, { action: "remove-study" });
- }
- }
- //stx.draw();
-};
-
-/**
- * Replaces an existing study with new inputs, outputs and parameters.
- *
- * When using this method a study's position in the stack will remain the same. Derived (child) studies will shift to use the new study as well
- * @param {CIQ.ChartEngine} stx The chart object
- * @param {string} id The id of the current study. If set, then the old study will be replaced
- * @param {string} type The name of the study (out of the studyLibrary)
- * @param {object} [inputs] Inputs for the study instance. Default is those defined in the studyLibrary.
- * @param {object} [outputs] Outputs for the study instance. Default is those defined in the studyLibrary.
- * @param {object} [parameters] additional custom parameters for this study if supported or required by that study
- * @param {string} [panelName] Optionally specify the panel. If not specified then an attempt will be made to locate a panel based on the input id or otherwise created if required.
- * @param {object} [study] Optionally supply a study definition, overriding what may be found in the study library
- * @return {object} A study descriptor which can be used to remove or modify the study.
- * @since 3.0.0 Added `study` parameter.
- * @memberof CIQ.Studies
- */
-CIQ.Studies.replaceStudy = function (
- stx,
- id,
- type,
- inputs,
- outputs,
- parameters,
- panelName,
- study
-) {
- if (!parameters) parameters = {};
- if (id) parameters.replaceID = id;
- id = parameters.replaceID;
- var sd = stx.layout.studies[id];
- CIQ.Studies.removeStudySymbols(sd, stx);
- if (sd.attribution) stx.removeFromHolder(sd.attribution.marker);
- if (stx.quoteDriver) stx.quoteDriver.updateSubscriptions();
- var newSD;
- if (inputs) {
- if (inputs.id == inputs.display) delete inputs.display;
- delete inputs.id;
- }
- newSD = CIQ.Studies.addStudy(
- stx,
- type,
- inputs,
- outputs,
- parameters,
- panelName,
- study
- );
- newSD.highlight = sd.highlight;
- newSD.uniqueId = sd.uniqueId;
-
- // move the new study into the place of the old study
- var s,
- tmp = {};
- for (s in stx.layout.studies) {
- if (s == id) tmp[newSD.name] = newSD;
- else tmp[s] = stx.layout.studies[s];
- }
- stx.layout.studies = tmp;
- tmp = {};
- for (s in stx.overlays) {
- if (s == id) {
- if (newSD.overlay || newSD.underlay) tmp[newSD.name] = newSD;
- } else tmp[s] = stx.overlays[s];
- }
- stx.overlays = tmp;
- if (!stx.overlays[newSD.name] && (newSD.overlay || newSD.underlay))
- stx.addOverlay(newSD);
-
- stx.checkForEmptyPanel(sd.panel); // close any evacuated panels
-
- if (!parameters.rejiggering) {
- // done to initialize yAxes on panels
- stx.initializeDisplay(stx.chart);
-
- // Rename any overlays that relied on the old panel ID name, for instance a moving average on RSI(14)
- CIQ.Studies.rejiggerDerivedStudies(stx, sd, newSD.inputs.id, newSD.panel);
-
- stx.changeOccurred("layout");
- if (
- !stx.currentlyImporting &&
- !parameters.calculateOnly &&
- newSD.chart.dataSet
- ) {
- // silent mode while importing
- stx.createDataSet(null, newSD.chart);
- }
- stx.draw();
- }
- CIQ.transferObject(sd, newSD); // we do this so the developer retains use of his handle to the study
- stx.layout.studies[newSD.name] = sd;
- stx.overlays[newSD.name] = sd;
- stx.chart.state.studies.sorted = null;
- return sd;
-};
-
-/**
- * Adds or replaces a study on the chart.
- *
- * A [layout change event]{@link CIQ.ChartEngine~layoutEventListener} is triggered when this occurs.
- *
- * See {@tutorial Using and Customizing Studies} for more details.
- *
- *
- * - Please note that these listeners must be set **before** you call importLayout. Otherwise your imported studies will not have an edit capability.
- *
- * Use the {@link CIQ.Tooltip} addOn if you wish to display values on mouse hover.
- * Alternatively, you can create your own Heads-Up-Display (HUD) using this tutorial: {@tutorial Custom Heads-Up-Display (HUD)}
- *
- * @param {CIQ.ChartEngine} stx The chart object
- * @param {string} type The name of the study (object key on the {@link CIQ.Studies.studyLibrary})
- * @param {object} [inputs] Inputs for the study instance. Default is those defined in the studyLibrary. Note that if you specify this object, it will be combined with (override) the library defaults. To bypass a library default, set that field to null.
- * @param {string} [inputs.id] The id of the current study. If set, then the old study will be replaced
- * @param {string} [inputs.display] The display name of the current study. If not set, a name generated by {@link CIQ.Studies.prettyDisplay} will be used. Note that if the study descriptor defines a `display` name, the study descriptor name will allays override this parameter.
- * @param {object} [outputs] Outputs for the study instance. Default is those defined in the studyLibrary. Values specified here will override those in the studyLibrary.
- * @param {object} [parameters] Additional custom parameters for this study if supported or required by that study. Default is those defined in the {@link CIQ.Studies.studyLibrary}.
- * @param {object} [parameters.replaceID] If `inputs.id` is specified, this value can be used to set the new ID for the modified study( will display as the study name on the study panel). If omitted the existing ID will be preserved.
- * @param {object} [parameters.display] If this is supplied, use it to form the full study name. Otherwise `studyName` will be used. Is both `inputs.display` and `parameters.display` are set, `inputs.display` will always take precedence.
ie: if custom name is 'SAMPLE', the unique name returned would resemble "SAMPLE(param1,param2,param3,...)-X".
- * @param {object} [parameters.calculateOnly] Only setup the study for calculations and not display. If this is supplied, UI elements will not be added.
- * @param {string} [panelName] Optionally specify the panel.
This must be an existing panel (see example).
If set to "New panel" a new panel will be created for the study. If not specified or an invalid panel name is provided, then an attempt will be made to locate a panel based on the input id or otherwise created if required. Multiple studies can be overlaid on any panel.
- * @param {object} [study] Study definition, overriding what may be found in the study library
- * @return {CIQ.Studies.StudyDescriptor} A study descriptor which can be used to remove or modify the study.
- * @since
- * - 3.0.0 Added `study` parameter.
- * - 5.1.1 Added `parameters.display`. If this parameter is supplied, use it to form the full study name.
- * - 5.2.0 Multiple studies can be overlaid on any panel using the `panelName` parameter.
- * - 6.3.0 `panelName` argument is deprecated but maintained for backwards compatibility. Use `parameters.panelName` instead.
- * - 7.1.0 Changed specification for a new panel in `panelName` from "Own panel" to "New panel".
- * @memberof CIQ.Studies
- * @example
- * Invoked by {@link CIQ.ChartEngine.renderYAxis} before createYAxis
- * @param {CIQ.ChartEngine} stx The chart object
- * @param {CIQ.ChartEngine.YAxis} yAxis The axis to act upon
- * @return {object} y-axis parameters such as noDraw, range, and ground
- * @memberof CIQ.Studies
- * @since 5.2.0
- */
-CIQ.Studies.getYAxisParameters = function (stx, yAxis) {
- var parameters = {};
- var sd = stx.layout.studies && stx.layout.studies[yAxis.name];
- if (sd) {
- var study = sd.study;
- if (study.yaxis || study.yAxisFN) {
- parameters.noDraw = true;
- } else {
- // If zones are enabled then we don't want to draw the yAxis
- if (study.parameters && study.parameters.excludeYAxis)
- parameters.noDraw = true;
- parameters.ground = study.yAxis && study.yAxis.ground;
- if (yAxis) {
- if (study.range != "bypass") {
- if (study.range == "0 to 100") parameters.range = [0, 100];
- else if (study.range == "-1 to 1") parameters.range = [-1, 1];
- else {
- if (study.range == "0 to max") {
- parameters.range = [0, Math.max(0, yAxis.high)];
- } else if (study.centerline || study.centerline === 0) {
- parameters.range = [
- Math.min(study.centerline, yAxis.low),
- Math.max(study.centerline, yAxis.high)
- ];
- }
- }
- }
- if (parameters.range) {
- yAxis.low = parameters.range[0];
- yAxis.high = parameters.range[1];
- }
- if (sd.min) yAxis.min = sd.min;
- if (sd.max) yAxis.max = sd.max;
- if (sd.parameters && sd.parameters.studyOverZonesEnabled)
- parameters.noDraw = true;
- }
- }
- }
- return parameters;
-};
-
-/**
- * studyOverZones will be displayed and Peaks & Valleys will be filled if corresponding thresholds are set in the study library as follows:
- * ```
- * "parameters": {
- * init:{studyOverZonesEnabled:true, studyOverBoughtValue:80, studyOverBoughtColor:"auto", studyOverSoldValue:20, studyOverSoldColor:"auto"}
- * }
- * ```
- * Invoked by {@link CIQ.ChartEngine.renderYAxis} after createYAxis
- * @param {CIQ.ChartEngine} stx The chart object
- * @param {CIQ.ChartEngine.YAxis} yAxis The axis to draw upon
- * @memberof CIQ.Studies
- * @since 5.2.0
- */
-CIQ.Studies.doPostDrawYAxis = function (stx, yAxis) {
- for (var s in stx.layout.studies) {
- var sd = stx.layout.studies[s];
- var panel = stx.panels[sd.panel];
- if (!panel || panel.hidden) continue;
- var studyAxis = sd.getYAxis(stx);
- if (studyAxis != yAxis) continue;
- var study = sd.study;
- if (yAxis.name == sd.name) {
- // only draw the custom yAxis for a panel study, not an overlay
- if (study.yaxis) study.yaxis(stx, sd); // backward compatibility
- if (study.yAxisFN) study.yAxisFN(stx, sd); // Use yAxisFN for forward compatibility
- }
- CIQ.Studies.drawZones(stx, sd);
-
- if (!sd.error) {
- var centerline = study.centerline;
- if (
- centerline ||
- centerline === 0 ||
- (centerline !== null && yAxis.highValue > 0 && yAxis.lowValue < 0)
- ) {
- CIQ.Studies.drawHorizontal(stx, sd, null, centerline || 0, yAxis);
- }
- }
- }
-};
-
-/**
- * Displays a single or group of series as lines in the study panel using {@link CIQ.Studies.displayIndividualSeriesAsLine}
- *
- * One series per output field declared in the study library will be displayed.
- * It expects the 'quotes' array to have data fields for each series with keys in the outputMap format:
- * ```
- * 'output name from study library'+ " " + sd.name
- * ```
- * For most custom studies this function will do the work for you.
- * @param {CIQ.ChartEngine} stx The chart object
- * @param {CIQ.Studies.StudyDescriptor} sd The study descriptor. See {@link CIQ.Studies.displayIndividualSeriesAsLine} for accepted `sd` parameters.
- * @param {array} quotes The set of quotes (dataSegment)
- * @memberof CIQ.Studies
- * @example
- * var study = {
- * overlay: true,
- * yAxis: {},
- * parameters: {
- * plotType: 'step',
- * },
- * seriesFN: function(stx, sd, quotes){
- * sd.extendToEnd=false;
- * sd.gaplines=false,
- * CIQ.Studies.displaySeriesAsLine(stx, sd, quotes);
- * }
- * };
- * CIQ.Studies.addStudy(stxx, "Vol", {}, {"Volume": "green"}, null, null, study);
- */
-CIQ.Studies.displaySeriesAsLine = function (stx, sd, quotes) {
- if (!quotes.length) return;
- var panel = stx.panels[sd.panel];
- if (!panel || panel.hidden) return;
-
- for (var i in sd.outputMap) {
- CIQ.Studies.displayIndividualSeriesAsLine(stx, sd, panel, i, quotes);
- }
-};
-
-/**
- * Displays a single or group of series as histogram in the study panel.
- *
- * It expects the 'quotes' array to have data fields for each series with keys in the outputMap
- * format:
- * ```
- * 'output name from study library'+ " " + sd.name
- * ```
- *
- * It takes into account the following study fields (see {@link CIQ.ChartEngine#drawHistogram}
- * for details):
- * - `sd.inputs.HistogramType` — "overlaid", "clustered", or "stacked". Default "overlaid".
- * - `sd.outputs` — Can contain a color string or an object containing `{color, opacity}`.
- * Default opacity ".3".
- * - `sd.parameters.widthFactor` — Default ".5".
- *
- * @param {CIQ.ChartEngine} stx The chart object.
- * @param {CIQ.Studies.StudyDescriptor} sd The study descriptor.
- * @param {array} quotes The set of quotes (`dataSegment`).
- *
- * @memberof CIQ.Studies
- * @since 7.0.0 No longer supports `sd.inputs.HeightPercentage`.
- * Use {@link CIQ.ChartEngine.YAxis#heightFactor} instead.
- *
- * @example
- *
- * Shading will be performed between the zone lines and the study plot.
- * @param {CIQ.ChartEngine} stx The chart object
- * @param {CIQ.Studies.StudyDescriptor} sd The study descriptor
- * @param {array} quotes unused
- * @memberof CIQ.Studies
- */
-CIQ.Studies.drawZones = function (stx, sd, quotes) {
- if (!sd.parameters || !sd.parameters.studyOverZonesEnabled) return;
-
- var low = parseFloat(sd.parameters.studyOverSoldValue);
- var high = parseFloat(sd.parameters.studyOverBoughtValue);
- var lowColor = sd.parameters.studyOverSoldColor;
- var highColor = sd.parameters.studyOverBoughtColor;
- var output = sd.zoneOutput;
- if (!output) output = "Result";
- var zoneColor = CIQ.Studies.determineColor(sd.outputs[output]);
- if (!zoneColor || zoneColor == "auto" || CIQ.isTransparent(zoneColor))
- zoneColor = stx.defaultColor;
- if (!lowColor) lowColor = zoneColor;
- if (!lowColor || lowColor == "auto" || CIQ.isTransparent(lowColor))
- lowColor = stx.defaultColor;
- if (!highColor) highColor = zoneColor;
- if (!highColor || highColor == "auto" || CIQ.isTransparent(highColor))
- highColor = stx.defaultColor;
- var panel = stx.panels[sd.panel];
- var yAxis = sd.getYAxis(stx);
- var drawBorders = yAxis.displayBorder;
- if (stx.axisBorders === false) drawBorders = false;
- if (stx.axisBorders === true) drawBorders = true;
- if (yAxis.width === 0) drawBorders = false;
- var yaxisPosition = stx.getYAxisCurrentPosition(yAxis, panel);
- var leftAxis = yaxisPosition == "left",
- rightJustify = yAxis.justifyRight;
- if (!rightJustify && rightJustify !== false) {
- if (
- stx.chart.yAxis.justifyRight ||
- stx.chart.yAxis.justifyRight === false
- ) {
- rightJustify = stx.chart.yAxis.justifyRight;
- } else rightJustify = leftAxis;
- }
- var borderEdge = Math.round(yAxis.left + (leftAxis ? yAxis.width : 0)) + 0.5;
- var tickWidth = drawBorders ? 3 : 0; // pixel width of tick off edge of border
-
- var ctx = stx.getBackgroundCanvas().context;
- var color = ctx.fillStyle;
-
- ctx.globalAlpha = 0.2;
-
- stx.startClip(panel.name, true);
-
- ctx.beginPath();
- var ph = Math.round(stx.pixelFromPrice(high, panel, yAxis)) + 0.5;
- ctx.strokeStyle = highColor;
- ctx.moveTo(panel.left, ph);
- ctx.lineTo(panel.right, ph);
- ctx.stroke();
- ctx.closePath();
-
- ctx.beginPath();
- var pl = Math.round(stx.pixelFromPrice(low, panel, yAxis)) + 0.5;
- ctx.strokeStyle = lowColor;
- ctx.moveTo(panel.left, pl);
- ctx.lineTo(panel.right, pl);
- ctx.stroke();
- ctx.closePath();
-
- var yAxisPlotter = new CIQ.Plotter();
- yAxisPlotter.newSeries(
- "border",
- "stroke",
- stx.canvasStyle("stx_grid_border")
- );
- if (drawBorders) {
- var tickLeft = leftAxis ? borderEdge - tickWidth : borderEdge - 0.5;
- var tickRight = leftAxis ? borderEdge + 0.5 : borderEdge + tickWidth;
- yAxisPlotter.moveTo("border", tickLeft, ph);
- yAxisPlotter.lineTo("border", tickRight, ph);
- yAxisPlotter.moveTo("border", tickLeft, pl);
- yAxisPlotter.lineTo("border", tickRight, pl);
- }
-
- ctx.fillStyle = color;
-
- var params = {
- skipTransform: stx.panels[sd.panel].name != sd.chart.name,
- panelName: sd.panel,
- band: output + " " + sd.name,
- yAxis: yAxis,
- opacity: 0.3
- };
- if (!sd.highlight && stx.highlightedDraggable) params.opacity *= 0.3;
- CIQ.preparePeakValleyFill(
- stx,
- CIQ.extend(params, {
- threshold: high,
- direction: yAxis.flipped ? -1 : 1,
- color: highColor
- })
- );
- CIQ.preparePeakValleyFill(
- stx,
- CIQ.extend(params, {
- threshold: low,
- direction: yAxis.flipped ? 1 : -1,
- color: lowColor
- })
- );
-
- ctx.globalAlpha = 1;
-
- if (!sd.study || !sd.study.yaxis) {
- if (drawBorders) {
- var b = Math.round(yAxis.bottom) + 0.5;
- yAxisPlotter.moveTo("border", borderEdge, yAxis.top);
- yAxisPlotter.lineTo("border", borderEdge, b);
- yAxisPlotter.draw(ctx, "border");
- }
-
- if (yAxis.width !== 0) {
- // Draw the y-axis with high/low
- stx.canvasFont("stx_yaxis", ctx);
- stx.canvasColor("stx_yaxis", ctx);
- ctx.textAlign = rightJustify ? "right" : "left";
- var textX;
- if (leftAxis) {
- textX = yAxis.left + 3;
- if (rightJustify) textX = yAxis.left + yAxis.width - tickWidth - 3;
- } else {
- textX = yAxis.left + tickWidth + 3;
- if (rightJustify) textX = yAxis.left + yAxis.width;
- }
- ctx.fillStyle = highColor;
- ctx.fillText(high, textX, ph);
- ctx.fillStyle = lowColor;
- ctx.fillText(low, textX, pl);
- ctx.fillStyle = color;
- }
- }
- stx.endClip();
- ctx.globalAlpha = 1;
-
- if (yAxis.name == sd.name) yAxis.yAxisPlotter = new CIQ.Plotter();
-};
-
-/**
- * Method used to display a histogram, which can be centered at the zero value.
- *
- * Used in studies such as on the "MACD" and "Klinger Volume Oscillator".
- *
- * Initial bar color is defined in stx-chart.css under '.stx_histogram'.
- * If using the default UI, refer to provided css files under '.stx_histogram' and '.ciq-night .stx_histogram' style sections.
- * If sd.outputs["Decreasing Bar"], sd.outputs["Negative Bar"], sd.outputs["Increasing Bar"] and sd.outputs["Positive Bar"] are present, their corresponding colors will be used instead.
- *
Example:
- * ```
- * CIQ.extend(CIQ.Studies.studyLibrary["vol undr"],{
- * "parameters": {
- * "widthFactor":0.5
- * }
- * });
- * ```
- *
- * Uses CSS style :
- * - `stx_volume_underlay` if "sd.underlay" is true
- * - `stx_volume` if "sd.underlay" is NOT true
- *
- * See {@link CIQ.ChartEngine#colorByCandleDirection} to base colors on difference between open and close vs. difference between previous close and close.
- *
- * @param {CIQ.ChartEngine} stx A chart engine instance
- * @param {CIQ.Studies.StudyDescriptor} sd A study descriptor
- * @param {array} quotes Array of quotes
- * @memberof CIQ.Studies
- * @example
- * // default volume study library entry with required parameters
- * "volume": {
- * "name": "Volume Chart",
- * "range": "0 to max",
- * "yAxis": {"ground":true, "initialMarginTop":0, "zoom":0},
- * "seriesFN": CIQ.Studies.createVolumeChart,
- * "calculateFN": CIQ.Studies.calculateVolume,
- * "inputs": {},
- * "outputs": {"Up Volume":"#8cc176","Down Volume":"#b82c0c"}
- * }
- * @example
- * // default volume underlay library entry with required parameters
- * "vol undr": {
- * "name": "Volume Underlay",
- * "underlay": true,
- * "range": "0 to max",
- * "yAxis": {"ground":true, "initialMarginTop":0, "position":"none", "zoom": 0, "heightFactor": 0.25},
- * "seriesFN": CIQ.Studies.createVolumeChart,
- * "calculateFN": CIQ.Studies.calculateVolume,
- * "inputs": {},
- * "outputs": {"Up Volume":"#8cc176","Down Volume":"#b82c0c"},
- * "customRemoval": true,
- * "removeFN": function(stx, sd){
- * stx.layout.volumeUnderlay=false;
- * stx.changeOccurred("layout");
- * },
- * "attributes":{
- * "panelName":{hidden:true}
- * }
- * }
- */
-CIQ.Studies.createVolumeChart = function (stx, sd, quotes) {
- var panel = sd.panel,
- inputs = sd.inputs,
- underlay = sd.underlay,
- overlay = sd.overlay;
- var inAnotherPanel = underlay || overlay;
- var colorUp = CIQ.Studies.determineColor(sd.outputs["Up Volume"]);
- var colorDown = CIQ.Studies.determineColor(sd.outputs["Down Volume"]);
- var style = underlay ? "stx_volume_underlay" : "stx_volume";
- stx.setStyle(style + "_up", "color", colorUp);
- stx.setStyle(style + "_down", "color", colorDown);
-
- var seriesParam = [
- {
- field: sd.volumeField || "Volume",
- fill_color_up: stx.canvasStyle(style + "_up").color,
- border_color_up: stx.canvasStyle(style + "_up").borderLeftColor,
- opacity_up: stx.canvasStyle(style + "_up").opacity,
- fill_color_down: stx.canvasStyle(style + "_down").color,
- border_color_down: stx.canvasStyle(style + "_down").borderLeftColor,
- opacity_down: stx.canvasStyle(style + "_down").opacity,
- color_function: sd.colorFunction
- }
- ];
- var seriesParam0 = seriesParam[0];
-
- // Major backward compatibility hack. If the border color is the same as our standard color
- // then most likely the customer is missing border: #000000 style on stx_volume_up and stx_volume_down
- if (!underlay && seriesParam0.border_color_down === "rgb(184, 44, 12)") {
- seriesParam0.border_color_down = "#000000";
- seriesParam0.border_color_up = "#000000";
- }
-
- var yAxis = sd.getYAxis(stx);
- var params = {
- name: "Volume",
- panel: panel,
- yAxis: yAxis,
- widthFactor: 1,
- bindToYAxis: true,
- highlight: sd.highlight
- };
-
- CIQ.extend(params, sd.study.parameters);
- CIQ.extend(params, sd.parameters);
-
- if (stx.colorByCandleDirection && !sd.colorFunction) {
- seriesParam0.color_function = function (quote) {
- var O = quote.Open,
- C = quote.Close;
- //if((!O && O!==0) || (!C && C!==0) || O===C) return stx.defaultColor;
-
- return {
- fill_color:
- O > C ? seriesParam0.fill_color_down : seriesParam0.fill_color_up,
- border_color:
- O > C ? seriesParam0.border_color_down : seriesParam0.border_color_up,
- opacity: O > C ? seriesParam0.opacity_down : seriesParam0.opacity_up
- };
- };
- }
-
- stx.drawHistogram(params, seriesParam);
-};
-
-/**
- * Calculate function for standard deviation.
- *
- * The resulting values will be added to the dataSet using the field name provided by the `sd.outputMap` entry.
- *
- * **Notes:**
- * - If no `outputs` object is defined in the library entry, the study will default to a single output named `Result`, which will then be used in lieu of `sd.outputs` to build the `sd.outputMap`.
- * - The study name may contain the unprintable character ``, see {@link CIQ.Studies.StudyDescriptor} documentation
- *
- * @param {CIQ.ChartEngine} stx Chart object
- * @param {CIQ.Studies.StudyDescriptor} sd Study Descriptor
- * @memberOf CIQ.Studies
- */
-CIQ.Studies.calculateStandardDeviation = function (stx, sd) {
- var quotes = sd.chart.scrubbed;
- if (quotes.length < sd.days + 1) {
- sd.error = true;
- return;
- }
- var field = sd.inputs.Field;
- if (!field || field == "field") field = "Close";
- var type = sd.inputs["Moving Average Type"];
- if (!type) type = sd.inputs.Type;
- CIQ.Studies.MA(type, sd.days, field, sd.inputs.Offset, "_MA", stx, sd);
-
- var acc1 = 0;
- var acc2 = 0;
- var ma = 0;
- var mult = Number(sd.inputs["Standard Deviations"]);
- if (mult < 0) mult = 2;
- var name = sd.name;
- for (var p in sd.outputs) {
- name = p + " " + name;
- }
- var i, val, its;
- for (i = sd.startFrom - 1, its = 0; i >= 0 && its < sd.days; i--, its++) {
- val = quotes[i][field];
- if (val && typeof val == "object") val = val[sd.subField];
- if (isNaN(val)) val = 0;
- acc1 += Math.pow(val, 2);
- acc2 += val;
- }
- for (i = sd.startFrom; i < quotes.length; i++) {
- var quote = quotes[i];
- val = quote[field];
- if (val && typeof val == "object") val = val[sd.subField];
- if (!val && val !== 0) val = 0;
- acc1 += Math.pow(val, 2);
- acc2 += val;
- if (i < sd.days - 1) continue;
- if (i >= sd.days) {
- var val2 = quotes[i - sd.days][field];
- if (val2 && typeof val2 == "object") val2 = val2[sd.subField];
- if (isNaN(val2)) val2 = 0;
- acc1 -= Math.pow(val2, 2);
- acc2 -= val2;
- }
- ma = quote["_MA " + sd.name];
- if (ma || ma === 0)
- quote[name] =
- Math.sqrt(
- (acc1 + sd.days * Math.pow(ma, 2) - 2 * ma * acc2) / sd.days
- ) * mult;
- }
-};
-
-/**
- * Calculate function for moving averages.
- *
- * sd.inputs["Type"] can be used to request a specific type of moving average. Valid options can be seen by inspecting the keys on the `CIQ.Studies.movingAverage.typeMap` object.
- *
- * The resulting values will be added to the dataSet using the field name provided by the `sd.outputMap` entry.
- *
- * **Notes:**
- * - This function calculates a single value, so it expects `sd.outputMap` to contain a single mapping.
- * - To leverage as part of a larger study calculation, use {@link CIQ.Studies.MA} instead.
- * - If no `outputs` object is defined in the library entry, the study will default to a single output named `Result`, which will then be used in lieu of `sd.outputs` to build the field name.
- * - The study name may contain the unprintable character ``, see {@link CIQ.Studies.StudyDescriptor} documentation.
- *
- *
- * @param {CIQ.ChartEngine} stx A chart engine instance
- * @param {CIQ.Studies.StudyDescriptor} sd A study descriptor
- * @memberOf CIQ.Studies
- */
-CIQ.Studies.calculateMovingAverage = function (stx, sd) {
- if (!sd.chart.scrubbed) return;
- var type = sd.inputs.Type;
- if (type == "ma" || type == "sma" || !type) type = "simple"; // handle when the default inputs are passed in
- var typeMap = CIQ.Studies.movingAverage.typeMap;
- if (type in typeMap) {
- return CIQ.Studies["calculateMovingAverage" + typeMap[type]](stx, sd);
- } else if (type !== "simple") {
- return;
- }
- var quotes = sd.chart.scrubbed;
- var acc = 0;
- var vals = [];
- var name = sd.name;
- for (var p in sd.outputs) {
- name = p + " " + name;
- }
- var field = sd.inputs.Field;
- if (!field || field == "field") field = "Close"; // Handle when the default inputs are passed in
- var offset = parseInt(sd.inputs.Offset, 10);
- if (isNaN(offset)) offset = 0;
- var i,
- val,
- ft,
- start = sd.startFrom;
- // backload the past data into the array
- var offsetBack = offset;
- for (i = sd.startFrom - 1; i >= 0; i--) {
- val = quotes[i][field];
- if (val && typeof val == "object") val = val[sd.subField];
- if (!val && val !== 0) continue;
- if (offsetBack > 0) {
- offsetBack--;
- start = i;
- continue;
- }
- if (vals.length == sd.days - 1) break;
- acc += val;
- vals.unshift(val);
- }
- if (vals.length < sd.days - 1) {
- vals = [];
- start = 0; // not enough records to continue where left off
- }
- var futureTicks = [];
- for (i = start; i < quotes.length; i++) {
- var quote = quotes[i];
- val = quote[field];
- if (val && typeof val == "object") val = val[sd.subField];
- var notOverflowing = i + offset >= 0 && i + offset < quotes.length;
- var offsetQuote = notOverflowing ? quotes[i + offset] : null;
- if (!val && val !== 0) {
- if (offsetQuote) offsetQuote[name] = null;
- else if (i + offset >= quotes.length) {
- ft = {};
- ft[name] = null;
- futureTicks.push(ft);
- }
- continue;
- }
- acc += val;
- vals.push(val);
- if (vals.length > sd.days) acc -= vals.shift();
- var myVal = vals.length == sd.days ? acc / sd.days : null;
- if (offsetQuote) offsetQuote[name] = myVal;
- else if (i + offset >= quotes.length) {
- ft = {};
- ft[name] = myVal;
- futureTicks.push(ft);
- }
- }
- sd.appendFutureTicks(stx, futureTicks);
-};
-
-/**
- * Calculate function for exponential moving average.
- *
- * The resulting values will be added to the dataSet using the field name provided by the `sd.outputMap` entry.
- *
- * **Notes:**
- * - This function calculates a single value, so it expects `sd.outputMap` to contain a single mapping.
- * - To leverage as part of a larger study calculation, use {@link CIQ.Studies.MA} instead.
- * - If no `outputs` object is defined in the library entry, the study will default to a single output named `Result`, which will then be used in lieu of `sd.outputs` to build the field name.
- * - The study name may contain the unprintable character ``, see {@link CIQ.Studies.StudyDescriptor} documentation.
- *
- * @param {CIQ.ChartEngine} stx Chart object
- * @param {CIQ.Studies.StudyDescriptor} sd Study Descriptor
- * @private
- * @memberof CIQ.Studies
- */
-CIQ.Studies.calculateMovingAverageExponential = function (stx, sd) {
- var type = sd.inputs.Type;
- var quotes = sd.chart.scrubbed;
- var acc = 0;
- var ma = 0;
- var ii = 0;
- var multiplier = 2 / (sd.days + 1);
- if (type === "welles wilder" || type === "smma") multiplier = 1 / sd.days;
-
- var emaPreviousDay = null;
- var name = sd.name;
- for (var p in sd.outputs) {
- name = p + " " + name;
- }
-
- var field = sd.inputs.Field;
- if (!field || field == "field") field = "Close"; // Handle when the default inputs are passed in
- var offset = parseInt(sd.inputs.Offset, 10);
- if (isNaN(offset)) offset = 0;
- var i, val;
- var start = sd.startFrom;
- // find emaPreviousDay
- var offsetBack = offset;
- for (i = sd.startFrom - 1; i >= 0; i--) {
- val = quotes[i][name];
- if (!val && val !== 0) continue;
- if (emaPreviousDay === null) emaPreviousDay = val;
- ii = sd.days;
- if (offsetBack <= 0) break;
- offsetBack--;
- start = i;
- }
- if (emaPreviousDay === null) {
- emaPreviousDay = start = 0;
- }
- var futureTicks = [];
- for (i = start; i < quotes.length; i++) {
- var quote = quotes[i];
- val = quote[field];
- if (val && typeof val == "object") val = val[sd.subField];
- var notOverflowing = i + offset >= 0 && i + offset < quotes.length;
- var offsetQuote = notOverflowing ? quotes[i + offset] : null;
- var myVal;
- if (!val && val !== 0) {
- myVal = null;
- } else {
- if (ii == sd.days - 1) {
- acc += val;
- ma = acc / sd.days;
- myVal = ma;
- } else if (ii < sd.days - 1) {
- acc += val;
- ma = acc / (ii + 1);
- myVal = null;
- } else if (ii === 0) {
- acc += val;
- ma = acc;
- myVal = null;
- } else if (emaPreviousDay || emaPreviousDay === 0) {
- ma = (val - emaPreviousDay) * multiplier + emaPreviousDay;
- myVal = ma;
- }
- emaPreviousDay = ma;
- ii++;
- }
- if (offsetQuote) offsetQuote[name] = myVal;
- else if (i + offset >= quotes.length) {
- var ft = {};
- ft[name] = myVal;
- futureTicks.push(ft);
- }
- }
- sd.appendFutureTicks(stx, futureTicks);
-};
-
-/**
- * Calculate function for VI Dynamic MA (VIDYA).
- *
- * The resulting values will be added to the dataSet using the field name provided by the `sd.outputMap` entry.
- *
- * **Notes:**
- * - This function calculates a single value, so it expects `sd.outputMap` to contain a single mapping.
- * - To leverage as part of a larger study calculation, use {@link CIQ.Studies.MA} instead.
- * - If no `outputs` object is defined in the library entry, the study will default to a single output named `Result`, which will then be used in lieu of `sd.outputs` to build the field name.
- * - The study name may contain the unprintable character ``, see {@link CIQ.Studies.StudyDescriptor} documentation.
- *
- * @param {CIQ.ChartEngine} stx Chart object
- * @param {CIQ.Studies.StudyDescriptor} sd Study Descriptor
- * @private
- * @memberof CIQ.Studies
- * @since 5.2.1
- */
-CIQ.Studies.calculateMovingAverageVIDYA = function (stx, sd) {
- var type = sd.inputs.Type;
- var quotes = sd.chart.scrubbed;
- var alpha = 2 / (sd.days + 1);
-
- var vmaPreviousDay = null;
- var name = sd.name;
- for (var p in sd.outputs) {
- name = p + " " + name;
- }
-
- var field = sd.inputs.Field;
- if (!field || field == "field") field = "Close"; // Handle when the default inputs are passed in
-
- sd.std = new CIQ.Studies.StudyDescriptor(sd.name, "sdev", sd.panel);
- sd.std.chart = sd.chart;
- sd.std.days = 5;
- sd.std.startFrom = sd.startFrom;
- sd.std.inputs = { Field: field, "Standard Deviations": 1, Type: "ma" };
- sd.std.outputs = { _STD: null };
- CIQ.Studies.calculateStandardDeviation(stx, sd.std);
-
- CIQ.Studies.MA("ma", 20, "_STD " + sd.name, 0, "_MASTD", stx, sd);
-
- var offset = parseInt(sd.inputs.Offset, 10);
- if (isNaN(offset)) offset = 0;
-
- var i, val, ft;
- var start = sd.startFrom;
- // find vmaPreviousDay
- var offsetBack = offset;
- for (i = sd.startFrom - 1; i >= 0; i--) {
- val = quotes[i][name];
- if (!val && val !== 0) continue;
- if (vmaPreviousDay === null) vmaPreviousDay = val;
- if (offsetBack <= 0) break;
- offsetBack--;
- start = i;
- }
- if (vmaPreviousDay === null) {
- vmaPreviousDay = start = 0;
- }
- var futureTicks = [];
- for (i = start; i < quotes.length; i++) {
- var quote = quotes[i];
- val = quote[field];
- if (val && typeof val == "object") val = val[sd.subField];
- var notOverflowing = i + offset >= 0 && i + offset < quotes.length;
- var offsetQuote = notOverflowing ? quotes[i + offset] : null;
- if (!val && val !== 0) {
- if (offsetQuote) offsetQuote[name] = null;
- else if (i + offset >= quotes.length) {
- ft = {};
- ft[name] = null;
- futureTicks.push(ft);
- }
- continue;
- }
- if (!quote["_MASTD " + sd.name] && quote["_MASTD " + sd.name] !== 0)
- continue;
- var vi = quote["_STD " + sd.name] / quote["_MASTD " + sd.name];
- var vma = alpha * vi * val + (1 - alpha * vi) * vmaPreviousDay;
- vmaPreviousDay = vma;
- if (i < sd.days) vma = null;
- if (offsetQuote) offsetQuote[name] = vma;
- else if (i + offset >= quotes.length) {
- ft = {};
- ft[name] = vma;
- futureTicks.push(ft);
- }
- }
- sd.appendFutureTicks(stx, futureTicks);
-};
-
-/**
- * Calculate function for triangular moving average.
- *
- * The resulting values will be added to the dataSet using the field name provided by the `sd.outputMap` entry.
- *
- * **Notes:**
- * - This function calculates a single value, so it expects `sd.outputMap` to contain a single mapping.
- * - To leverage as part of a larger study calculation, use {@link CIQ.Studies.MA} instead.
- * - If no `outputs` object is defined in the library entry, the study will default to a single output named `Result`, which will then be used in lieu of `sd.outputs` to build the field name.
- * - The study name may contain the unprintable character ``, see {@link CIQ.Studies.StudyDescriptor} documentation.
- *
- * @param {CIQ.ChartEngine} stx Chart object
- * @param {CIQ.Studies.StudyDescriptor} sd Study Descriptor
- * @private
- * @memberof CIQ.Studies
- */
-CIQ.Studies.calculateMovingAverageTriangular = function (stx, sd) {
- var quotes = sd.chart.scrubbed;
-
- var field = sd.inputs.Field;
- if (!field || field == "field") field = "Close"; // Handle when the default inputs are passed in
- var days = Math.ceil(sd.days / 2);
- CIQ.Studies.MA("simple", days, field, 0, "TRI1", stx, sd);
- if (sd.days % 2 === 0) days++;
- CIQ.Studies.MA("simple", days, "TRI1 " + sd.name, 0, "TRI2", stx, sd);
-
- var name = sd.name;
- for (var p in sd.outputs) {
- name = p + " " + name;
- }
- var offset = parseInt(sd.inputs.Offset, 10);
- if (isNaN(offset)) offset = 0;
-
- // find start
- var offsetBack = offset;
- for (var i = sd.startFrom - 1; i >= 0; i--) {
- var val = quotes[i][name];
- if (!val && val !== 0) continue;
- if (offsetBack > 0) {
- offsetBack--;
- continue;
- }
- break;
- }
- var futureTicks = [];
- for (i++; i < quotes.length; i++) {
- if (i + offset >= 0) {
- if (i + offset < quotes.length)
- quotes[i + offset][name] = quotes[i]["TRI2 " + sd.name];
- else {
- var ft = {};
- ft[name] = quotes[i]["TRI2 " + sd.name];
- futureTicks.push(ft);
- }
- }
- }
- sd.appendFutureTicks(stx, futureTicks);
-};
-
-/**
- * Calculate function for weighted moving average.
- *
- * The resulting values will be added to the dataSet using the field name provided by the `sd.outputMap` entry.
- *
- * **Notes:**
- * - This function calculates a single value, so it expects `sd.outputMap` to contain a single mapping.
- * - To leverage as part of a larger study calculation, use {@link CIQ.Studies.MA} instead.
- * - If no `outputs` object is defined in the library entry, the study will default to a single output named `Result`, which will then be used in lieu of `sd.outputs` to build the field name.
- * - The study name may contain the unprintable character ``, see {@link CIQ.Studies.StudyDescriptor} documentation.
- *
- * @param {CIQ.ChartEngine} stx Chart object
- * @param {CIQ.Studies.StudyDescriptor} sd Study Descriptor
- * @private
- * @memberof CIQ.Studies
- */
-CIQ.Studies.calculateMovingAverageWeighted = function (stx, sd) {
- var quotes = sd.chart.scrubbed;
-
- var accAdd = 0;
- var accSubtract = 0;
- var field = sd.inputs.Field;
- if (!field || field == "field") field = "Close"; // Handle when the default inputs are passed in
- var divisor = (sd.days * (sd.days + 1)) / 2;
-
- var name = sd.name;
- for (var p in sd.outputs) {
- name = p + " " + name;
- }
- var offset = parseInt(sd.inputs.Offset, 10);
- if (isNaN(offset)) offset = 0;
- var i, val, ft;
- var vals = [];
- var start = sd.startFrom;
- // backload the past data into the array
- var offsetBack = offset;
- for (i = sd.startFrom - 1; i >= 0; i--) {
- val = quotes[i][field];
- if (val && typeof val == "object") val = val[sd.subField];
- if (!val && val !== 0) continue;
- if (offsetBack > 0) {
- offsetBack--;
- start = i;
- continue;
- }
- if (vals.length == sd.days - 1) break;
- vals.unshift(val);
- }
- if (vals.length < sd.days - 1) {
- vals = [];
- start = 0; // not enough records to continue where left off
- }
- for (i = 0; i < vals.length; i++) {
- accAdd += (i + 1) * vals[i];
- accSubtract += vals[i];
- }
- var futureTicks = [];
- for (i = start; i < quotes.length; i++) {
- var quote = quotes[i];
- val = quote[field];
- if (val && typeof val == "object") val = val[sd.subField];
- var notOverflowing = i + offset >= 0 && i + offset < quotes.length;
- var offsetQuote = notOverflowing ? quotes[i + offset] : null;
- if (!val && val !== 0) {
- if (offsetQuote) offsetQuote[name] = null;
- else if (i + offset >= quotes.length) {
- ft = {};
- ft[name] = null;
- futureTicks.push(ft);
- }
- continue;
- }
- vals.push(val);
- if (vals.length > sd.days) {
- accAdd -= accSubtract;
- accSubtract -= vals.shift();
- }
- accAdd += vals.length * val;
- accSubtract += val;
-
- var myVal = i < sd.days - 1 ? null : accAdd / divisor;
- if (offsetQuote) offsetQuote[name] = myVal;
- else if (i + offset >= quotes.length) {
- ft = {};
- ft[name] = myVal;
- futureTicks.push(ft);
- }
- }
- sd.appendFutureTicks(stx, futureTicks);
-};
-
-CIQ.Studies.calculateStudyATR = function (stx, sd) {
- var quotes = sd.chart.scrubbed;
- var period = sd.days;
- if (quotes.length < period + 1) {
- sd.error = true;
- return;
- }
- var total = 0;
- var name = sd.name;
- for (var i = Math.max(sd.startFrom, 1); i < quotes.length; i++) {
- var prices = quotes[i];
- var pd = quotes[i - 1];
- var trueRange = prices.trueRange;
- if (pd["Sum True Range " + name]) total = pd["Sum True Range " + name];
- total += trueRange;
- if (i > period) total -= quotes[i - period]["True Range " + name];
- prices["True Range " + name] = trueRange;
- prices["Sum True Range " + name] = total;
- if (i == period) prices["ATR " + name] = total / period;
- else if (i > period)
- prices["ATR " + name] =
- (pd["ATR " + name] * (period - 1) + trueRange) / period;
- }
-};
-
-/**
- * Default display function used on 'ATR Trailing Stop' and 'Parabolic SAR' studies to display a series of 'dots' at the required price-date coordinates.
- *
- * Visual Reference:
- * ![displayPSAR2](img-displayPSAR2.png "displayPSAR2")
- *
- * @param {CIQ.ChartEngine} stx A chart engine instance
- * @param {CIQ.Studies.StudyDescriptor} sd
- * @param {array} quotes Array of quotes
- * @memberOf CIQ.Studies
- */
-CIQ.Studies.displayPSAR2 = function (stx, sd, quotes) {
- var panel = stx.panels[sd.panel];
- var yAxis = sd.getYAxis(stx);
- var sharingChartAxis = yAxis == stx.chart.panel.yAxis;
- stx.startClip(panel.name);
- var ctx = sd.getContext(stx);
- var squareWave = sd.inputs["Plot Type"] == "squarewave";
- for (var output in sd.outputs) {
- var field = output + " " + sd.name;
- ctx.beginPath();
- var candleWidth = stx.layout.candleWidth;
- var pointWidth = Math.max(3, Math.floor(stx.chart.tmpWidth / 2));
- for (var x = 0; x < quotes.length; x++) {
- var quote = quotes[x];
- if (!quote || (!quote[field] && quote[field] !== 0)) continue;
- if (quote.candleWidth) candleWidth = quote.candleWidth;
- if (sharingChartAxis && quote.transform) quote = quote.transform;
- var x0 = stx.pixelFromBar(x, panel.chart);
- if (squareWave) x0 -= candleWidth / 2;
- var y0 = stx.pixelFromTransformedValue(
- quote[sd.referenceOutput ? sd.referenceOutput + " " + sd.name : field],
- panel,
- yAxis
- );
- if (
- x === 0 ||
- !quotes[x - 1] ||
- (!quotes[x - 1][field] && quotes[x - 1][field] !== 0)
- ) {
- ctx.moveTo(x0, y0);
- }
- if (squareWave) {
- ctx.lineTo(x0, y0);
- ctx.lineTo(x0 + candleWidth, y0);
- if (quotes[x + 1]) {
- var quote_1 = quotes[x + 1];
- if (sharingChartAxis && quote_1.transform)
- quote_1 = quote_1.transform;
- if (!quote_1[field] && quote_1[field] !== 0) {
- ctx.lineTo(
- x0 + candleWidth,
- stx.pixelFromTransformedValue(
- quote_1[
- sd.referenceOutput
- ? sd.referenceOutput + " " + sd.name
- : field
- ],
- stx.panels[sd.panel],
- yAxis
- )
- );
- }
- }
- } else {
- ctx.moveTo(x0 - pointWidth / 2, y0);
- ctx.lineTo(x0 + pointWidth / 2, y0);
- }
- }
- ctx.lineWidth = 1;
- if (sd.highlight) ctx.lineWidth = 3;
- var color = CIQ.Studies.determineColor(sd.outputs[output]);
- if (color == "auto") color = stx.defaultColor; // This is calculated and set by the kernel before draw operation.
- ctx.strokeStyle = color;
- if (!sd.highlight && stx.highlightedDraggable) ctx.globalAlpha *= 0.3;
- ctx.stroke();
- ctx.closePath();
- ctx.lineWidth = 1;
- }
- stx.endClip();
-};
-
-CIQ.Studies.inputAttributeDefaultGenerator = function (value) {
- if (!value && value !== 0) return {};
- if (value.constructor == Number) {
- if (Math.floor(value) == value) {
- // Integer
- if (value > 0) return { min: 1, step: 1 }; // positive
- return { step: 1 }; // full range
- }
- // Decimal
- if (value > 0) return { min: 0, step: 0.01 }; // positive
- return { step: 0.01 }; // full range
- }
- return {};
-};
-
-/**
- * Gets the difference between the local browser time and the market time.
- *
- * @param {object} params Function parameters.
- * @param {CIQ.ChartEngine} params.stx A reference to the chart object.
- * @param {object} params.localQuoteDate A Date object that contains the market date and time.
- * @param {boolean} params.shiftToDateBoundary Indicates whether the offset for FOREX symbols
- * should be adjusted such that the beginning of the trading day (17:00 New York time) falls
- * on a date boundary; if so, adds seven hours to the date/time (six for metals). **Note:**
- * This parameter applies to FOREX symbols only. No additional time offset is added to
- * non-FOREX symbols, regardless of the value of this parameter.
- * @return {number} The local browser date/time minus the market date/time in milliseconds.
- *
- * @memberof CIQ.Studies
- * @since
- * - 8.0.0
- * - 8.1.0 Removed `isForex` parameter. Added `shiftToDateBoundary` parameter. Added `params`
- * parameter and made all other parameters properties of `params`.
- */
-CIQ.Studies.getMarketOffset = function ({
- stx,
- localQuoteDate,
- shiftToDateBoundary
-}) {
- let isForex; // defer to passed value if present
- if (arguments.length > 1) {
- stx = arguments[0];
- localQuoteDate = arguments[1];
- isForex = arguments[2];
- }
-
- const { symbol } = stx.chart;
- const isMetal = CIQ.getFn("Market.Symbology.isForexMetal")(symbol);
- if (isForex === undefined) {
- isForex = CIQ.getFn("Market.Symbology.isForexSymbol")(symbol);
- }
-
- let marketZone;
- if (!stx.chart.market) marketZone = null;
- else marketZone = isForex ? "America/New_York" : stx.chart.market.market_tz;
-
- var dt = new Date(
- localQuoteDate.getTime() + localQuoteDate.getTimezoneOffset() * 60000
- );
- if (!marketZone || marketZone.indexOf("UTC") == -1)
- dt = CIQ.convertTimeZone(dt, "UTC", marketZone);
-
- let marketOffset =
- new Date(
- dt.getFullYear(),
- dt.getMonth(),
- dt.getDate(),
- dt.getHours(),
- dt.getMinutes(),
- dt.getSeconds(),
- dt.getMilliseconds()
- ).getTime() - localQuoteDate.getTime();
-
- if (shiftToDateBoundary && isForex)
- marketOffset += (isMetal ? 6 : 7) * 60 * 60 * 1000;
- return marketOffset;
-};
-
-/**
- * Function to determine which studies are available.
- * @param {object} excludeList Exclusion list of studies in object form ( e.g. {"rsi":true,"macd":true})
- * @returns {object} Map of available entries from {@link CIQ.Studies.studyLibrary}.
- * @memberof CIQ.Studies
- * @since 3.0.0
- */
-CIQ.Studies.getStudyList = function (excludeList) {
- var map = {};
- var excludedStudies = {}; // from time to time put old studies in here to not list them
- CIQ.extend(excludedStudies, excludeList);
- for (var libraryEntry in CIQ.Studies.studyLibrary) {
- if (!excludedStudies[libraryEntry])
- map[CIQ.Studies.studyLibrary[libraryEntry].name] = libraryEntry;
- }
- return map;
-};
-
-/**
- * A helper function that will find the color value in the output.
- * @param {string|object} output Color string value or object that has the color value
- * @return {string} Color value
- * @memberof CIQ.Studies
- * @since 4.0.0
- */
-CIQ.Studies.determineColor = function (output) {
- if (!output) {
- return null;
- } else if (typeof output === "object") {
- return output.color;
- }
-
- return output;
-};
-
-/**
- * Calculate function for preparing data to be used by displayChannel().
- *
- * Inserts the following fields in the dataSet:
- * ```
- * quote[sd.type + " Top " + sd.name]=quote[centerIndex]+totalShift;
- * quote[sd.type + " Bottom " + sd.name]=quote[centerIndex]-totalShift;
- * quote[sd.type + " Median " + sd.name]=quote[centerIndex];
- * quote["Bandwidth " + sd.name]=200*totalShift/quote[centerIndex];
- * quote["%b " + sd.name]=50*((quote.Close-quote[centerIndex])/totalShift+1);
- * ```
- * Example: 'Prime Bands' + ' Top ' + 'Prime Number Bands (true)'.
- * @param {CIQ.ChartEngine} stx Chart object
- * @param {CIQ.Studies.StudyDescriptor} sd Study Descriptor
- * @param {object} percentShift Used to calculate totalShift. Defaults to 0 (zero)
- * @param {object} [centerIndex=Close] Quote element to use for center series (Open, Close, High, Low). Defaults to "Close"
- * @param {object} [offsetIndex=centerIndex] Quote element to use for calculating totalShift (percentShift*quote[offsetIndex]+pointShift;)
- * @param {object} [pointShift=0] Used to calculate totalShift.Defaults to 0 (zero)
- * @memberOf CIQ.Studies
- */
-CIQ.Studies.calculateGenericEnvelope = function (
- stx,
- sd,
- percentShift,
- centerIndex,
- offsetIndex,
- pointShift
-) {
- if (!percentShift) percentShift = 0;
- if (!pointShift) pointShift = 0;
- if (!centerIndex || centerIndex == "field") centerIndex = "Close";
- if (!offsetIndex) offsetIndex = centerIndex;
- var quotes = sd.chart.scrubbed;
- var field = sd.inputs.Field;
- if (!field || field === "field") field = "Close";
- for (var i = sd.startFrom; quotes && i < quotes.length; i++) {
- var quote = quotes[i];
- if (!quote) continue;
- if (!quote[centerIndex]) continue;
- var closeValue = quote[field];
- if (closeValue && typeof closeValue == "object")
- closeValue = closeValue.Close;
- var centerValue = quote[centerIndex];
- if (centerValue && typeof centerValue == "object")
- centerValue = centerValue[sd.subField];
- var offsetValue = quote[offsetIndex];
- if (offsetValue && typeof offsetValue == "object")
- offsetValue = offsetValue[sd.subField];
- var totalShift = percentShift * offsetValue + pointShift;
- quote[sd.type + " Top " + sd.name] = centerValue + totalShift;
- quote[sd.type + " Bottom " + sd.name] = centerValue - totalShift;
- quote[sd.type + " Median " + sd.name] = centerValue;
- quote["Bandwidth " + sd.name] = centerValue
- ? (200 * totalShift) / centerValue
- : 0;
- quote["%b " + sd.name] = 50 * ((closeValue - centerValue) / totalShift + 1);
- }
-};
-
-/**
- * Rendering function for displaying a Channel study output composed of top, middle and bottom lines.
- *
- * Requires study library input of `"Channel Fill":true` to determine if the area within the channel is to be shaded.
- * Shading will be done using the "xxxxx Channel" or "xxxxx Median" color defined in the outputs parameter of the study library.
- *
- * Requires study library outputs to have fields in the format of :
- * - 'xxxxx Top' or 'xxxxx High' for the top band,
- * - 'xxxxx Bottom' or 'xxxxx Low' for the bottom band and
- * - 'xxxxx Median' or 'xxxxx Channel' for the middle line.
- *
- * It expects 'quotes' to have fields for each series in the channel with keys in the following format:
- * - study-output-name ( from study library) + " " + sd.name.
- * - Example: 'Prime Bands Top'+ ' ' + 'Prime Number Bands (true)'. Which equals : 'Prime Bands Top Prime Number Bands (true)'
- *
- * @param {CIQ.ChartEngine} stx Chart object
- * @param {CIQ.Studies.StudyDescriptor} sd Study Descriptor
- * @param {array} quotes The array of quotes needed to render the channel
- * @memberOf CIQ.Studies
- * @example
- * "inputs": {"Period":5, "Shift": 3, "Field":"field", "Channel Fill":true}
- * "outputs": {"Prime Bands Top":"red", "Prime Bands Bottom":"auto", "Prime Bands Channel":"rgb(184,44,11)"}
- * @example
- * // full definition example including opacity
- "Bollinger Bands": {
- "name": "Bollinger Bands",
- "overlay": true,
- "calculateFN": CIQ.Studies.calculateBollinger,
- "seriesFN": CIQ.Studies.displayChannel,
- "inputs": {"Field":"field", "Period":20, "Standard Deviations": 2, "Moving Average Type":"ma", "Channel Fill": true},
- "outputs": {"Bollinger Bands Top":"auto", "Bollinger Bands Median":"auto", "Bollinger Bands Bottom":"auto"},
- "attributes": {
- "Standard Deviations":{min:0.1,step:0.1}
- },
- "parameters": {
- "init":{opacity: 0.2}
- }
- }
- * @since
- * - 4.1.0 Now also uses `sd.parameters.opacity` if one defined.
- * - 4.1.0 Now shading is rendered under the channel lines instead of over.
- */
-CIQ.Studies.displayChannel = function (stx, sd, quotes) {
- if (sd.inputs["Channel Fill"]) {
- var parameters = { panelName: sd.panel };
- for (var p in sd.outputs) {
- var lastWord = p.split(" ").pop();
- if (lastWord == "Top" || lastWord == "High") {
- parameters.topBand = p + " " + sd.name;
- } else if (lastWord == "Bottom" || lastWord == "Low") {
- parameters.bottomBand = p + " " + sd.name;
- } else if (lastWord == "Median" || lastWord == "Channel") {
- parameters.color = CIQ.Studies.determineColor(sd.outputs[p]);
- }
- }
- if (sd.parameters && sd.parameters.opacity) {
- parameters.opacity = sd.parameters.opacity;
- } else {
- parameters.opacity = 0.2;
- }
- var panel = stx.panels[sd.panel];
- parameters.skipTransform = panel.name != sd.chart.name;
- parameters.yAxis = sd.getYAxis(stx);
- if (!sd.highlight && stx.highlightedDraggable) parameters.opacity *= 0.3;
-
- CIQ.prepareChannelFill(stx, parameters);
- }
- CIQ.Studies.displaySeriesAsLine(stx, sd, quotes);
-};
-
-/**
- * Initializes an anchor handle element on a study and adds the anchor element to the chart
- * controls. If the anchor element and study already exist but the study object has changed, the
- * existing anchor element is added to the new study object. **Note:** A study object may change
- * without its unique ID changing.
- *
- * @param {CIQ.ChartEngine} stx A reference to the chart object.
- * @param {CIQ.Studies.StudyDescriptor} sd Specifies a study object.
- *
- * @memberof CIQ.Studies
- * @since 8.1.0
- */
-CIQ.Studies.initAnchorHandle = function (stx, sd) {
- let { handle } = sd;
- if (handle) return;
-
- if (!stx.controls.anchorHandles) stx.controls.anchorHandles = {};
-
- if (stx.controls.anchorHandles[sd.uniqueId]) {
- ({ handle } = stx.controls.anchorHandles[sd.uniqueId]);
- } else {
- handle = document.createElement("div");
- handle.classList.add("stx_anchor_handle");
- handle.setAttribute(sd.uniqueId, "");
- stx.controls.anchorHandles[sd.uniqueId] = { handle, sd };
- stx.controls.chartControls.parentElement.appendChild(handle);
- }
-
- sd.anchorHandle = handle;
-};
-
-/**
- * Removes an anchor handle element from the specified study.
- *
- * @param {CIQ.ChartEngine} stx A reference to the chart object.
- * @param {CIQ.Studies.StudyDescriptor} sd Specifies a study object.
- *
- * @memberof CIQ.Studies
- * @since 8.1.0
- */
-CIQ.Studies.removeAnchorHandle = function (stx, sd) {
- const { handle } = (stx.controls.anchorHandles || {})[sd.uniqueId] || {};
- if (handle) {
- delete stx.controls.anchorHandles[sd.uniqueId];
- handle.remove();
- }
-};
-
-/**
- * Repositions the anchor for a study to the tick where the anchor element has been dragged. This
- * causes the study to be recalculated. If there is no hover location (the anchor has not been
- * dragged), the study is recalcuated without changing the anchor.
- *
- * @param {CIQ.ChartEngine} stx A reference to the chart object.
- * @param {CIQ.Studies.StudyDescriptor} sd Specifies a study object.
- *
- * @memberof CIQ.Studies
- * @since 8.1.0
- */
-CIQ.Studies.repositionAnchor = function (stx, sd) {
- const { currentAnchorTime, uniqueId } = sd;
- const { hoverTick } = stx.repositioningAnchorSelector || {};
- const { dataSet, market } = stx.chart;
- const { anchorHandles } = stx.controls;
- let newInputs = {};
-
- if (hoverTick || hoverTick === 0) {
- if (hoverTick >= dataSet.length) return;
-
- const isDaily = !sd.inputs["Anchor Date"];
- let hoverDate = dataSet[hoverTick].DT;
-
- const marketOffset = CIQ.Studies.getMarketOffset({
- stx,
- localQuoteDate: hoverDate,
- shiftToDateBoundary: true
- });
-
- if (
- isDaily &&
- new Date(hoverDate.getTime() + marketOffset).getDate() !==
- new Date(currentAnchorTime.getTime() + marketOffset).getDate()
- ) {
- return;
- }
-
- if (market.market_def) {
- hoverDate = new timezoneJS.Date(hoverDate, market.market_def.market_tz);
- }
-
- const newAnchorDate = !isDaily && CIQ.dateToStr(hoverDate, "YYYY-MM-dd");
- const newAnchorTime = CIQ.dateToStr(hoverDate, "HH:mm:ss");
- newInputs = { "Anchor Time": newAnchorTime };
- if (newAnchorDate) newInputs["Anchor Date"] = newAnchorDate;
- }
-
- const newSd = CIQ.Studies.replaceStudy(
- stx,
- sd.inputs.id,
- sd.type,
- Object.assign(sd.inputs, newInputs),
- sd.outputs,
- sd.parameters,
- sd.panel
- );
-
- anchorHandles[uniqueId].sd = newSd;
- stx.draw();
-};
-
-/**
- * Displays the anchor element at its current location and a line depicting the hover location of
- * the anchor as it is being dragged. Called as part of the draw loop.
- *
- * @param {CIQ.ChartEngine} stx A reference to the chart object.
- * @param {CIQ.Studies.StudyDescriptor} sd Specifies a study object.
- * @param {array} quotes The quotes (`dataSegment`) array.
- *
- * @memberof CIQ.Studies
- * @since 8.1.0
- */
-CIQ.Studies.displayAnchorHandleAndLine = function (stx, sd, quotes) {
- const currentPanelDragging =
- (stx.repositioningAnchorSelector || {}).sd === sd;
- const { hoverTick } = currentPanelDragging && stx.repositioningAnchorSelector;
- const { chart, panels, xaxisHeight } = stx;
- const { market = {}, symbol } = chart;
- const panel = panels[sd.panel];
- const { top, bottom, right, left, height } = panel;
- const { inputs, anchorHandle: handle, currentAnchorTime } = sd;
- const {
- backgroundColor: color,
- borderLeftColor: colorInvalid
- } = stx.canvasStyle("stx_anchor_handle");
- const [hh, mm, ss = 0] = (inputs["Anchor Time"] || "").split(":");
- const isDaily = !inputs["Anchor Date"]; // for anchors without a date
- const isForex = CIQ.getFn("Market.Symbology.isForexSymbol")(symbol);
- const hoverDate =
- (hoverTick || hoverTick === 0) && (stx.chart.dataSet[hoverTick] || {}).DT;
- const marketOffset = CIQ.Studies.getMarketOffset({
- stx,
- localQuoteDate: quotes[quotes.length - 1].DT,
- shiftToDateBoundary: true
- });
- const hoverOutOfBounds =
- hoverDate &&
- isDaily &&
- new Date(hoverDate.getTime() + marketOffset).getDate() !==
- new Date(currentAnchorTime.getTime() + marketOffset).getDate();
- const { highlighted } = stx.controls.anchorHandles[sd.uniqueId];
- const [normalOpenHours, normalOpenMins] = market
- .getNormalOpen()
- .split(":")
- .map((x) => parseInt(x));
-
- const getPixel = (dt) => {
- let tick = dt ? stx.tickFromDate(dt, null, null, true) : hoverTick;
- return [stx.pixelFromTick(tick, chart), tick];
- };
-
- let anchorTime = isDaily
- ? new Date(quotes[quotes.length - 1].DT) // if no anchor date diplay at right most
- : CIQ.strToDate(inputs["Anchor Date"]);
-
- if (market.market_def) {
- anchorTime = new timezoneJS.Date(anchorTime, market.market_def.market_tz);
- }
-
- anchorTime.setHours(hh, mm, ss);
-
- // This will allow us to shift the anchor and end of day to compensate for the midnight-bisected
- // nature of FOREX market sessions
- const firstSection =
- isForex &&
- (anchorTime.getHours() > normalOpenHours ||
- (anchorTime.getHours() === normalOpenHours &&
- anchorTime.getMinutes() >= normalOpenMins));
-
- if (firstSection) anchorTime.setDate(anchorTime.getDate() - 1);
-
- let [hoverPixel] = getPixel();
- let [currentPixel, currentTick] = getPixel(anchorTime);
- let endOfDay = new Date(anchorTime);
- endOfDay.setHours(...market.getNormalClose().split(":"));
- if (firstSection) endOfDay.setDate(endOfDay.getDate() + 1);
-
- const [endOfDayPixel] = (endOfDay && getPixel(endOfDay)) || [];
-
- if (isDaily && (currentPixel > right || endOfDayPixel > right)) {
- // rewind if necessary to get anchor on day that is fully visible
- let shiftedAnchor = new Date(anchorTime);
- do {
- shiftedAnchor.setDate(shiftedAnchor.getDate() - 1);
- } while (market && !market.isMarketDate(shiftedAnchor));
- // if new position is off the left of the chart don't both shifting
- let [shiftedPixel, shiftedTick] = getPixel(shiftedAnchor);
- if (shiftedPixel > left) {
- anchorTime = shiftedAnchor;
- currentPixel = shiftedPixel;
- currentTick = shiftedTick;
- }
- }
-
- const lineConfig = {
- y0: top,
- y1: bottom,
- type: "line",
- confineToPanel: panel
- };
-
- stx.plotLine(
- Object.assign(lineConfig, {
- x0: currentPixel,
- x1: currentPixel,
- color: color,
- pattern: "solid",
- lineWidth: highlighted ? 3 : 2,
- opacity: 1
- })
- );
-
- stx.plotLine(
- Object.assign(lineConfig, {
- x0: hoverPixel,
- x1: hoverPixel,
- color: hoverOutOfBounds ? colorInvalid : color,
- pattern: [6, 6],
- lineWidth: 2,
- opacity: hoverOutOfBounds ? 0.5 : 1
- })
- );
-
- handle.style.height = [8, height / 4, 50].sort((a, b) => a - b)[1] + "px"; // use middle value
- const { height: aHeight, width: aWidth } = handle.getBoundingClientRect();
- const isBottomOffset = Math.round(bottom) === stx.height ? xaxisHeight : 0;
- const isChartPanelOffset = panel.name === "chart" ? 35 : 0;
- const verticalOffset = 10 + isBottomOffset + isChartPanelOffset + aHeight;
-
- handle.style.top = bottom - verticalOffset + "px";
- handle.style.left =
- hoverTick || hoverTick === 0
- ? hoverPixel
- : currentPixel - aWidth / 2 + "px";
-
- sd.currentAnchorTime = anchorTime;
- sd.currentAnchorTick = currentTick;
-};
-
-// object to keep track of the custom scripts
-CIQ.Studies.studyScriptLibrary = {};
-
-/**
- * The `studyLibrary` defines all of the available studies.
- *
- * This is used to drive the dialog boxes and creation of the studies. When you
- * create a custom study you should add it to the studyLibrary.
- *
- * You can also alter study defaults by overriding the different elements on each definition.
- * For example, if you wanted to change the default colors for the volume underlay,
- * you would add the following code in your files; making sure your files are loaded **after** the library js files -- not before:
- * ```
- * CIQ.Studies.studyLibrary["vol undr"].outputs= {"Up Volume":"blue","Down Volume":"yellow"};
- * ```
- * See {@tutorial Using and Customizing Studies} for complete details.
- * @type {Object}
- * @memberof CIQ.Studies
- * @example
- * "RAVI": {
- * "name": "RAVI",
- * "seriesFN": CIQ.Studies.displayRAVI,
- * "calculateFN": CIQ.Studies.calculatePriceOscillator,
- * "inputs": {"Field":"field", "Short Cycle":7, "Long Cycle":65},
- * "outputs": {"Increasing Bar":"#00DD00", "Decreasing Bar":"#FF0000"},
- * "parameters": {
- * init:{studyOverZonesEnabled:true, studyOverBoughtValue:3, studyOverBoughtColor:"auto", studyOverSoldValue:-3, studyOverSoldColor:"auto"}
- * },
- * "attributes":{
- * "studyOverBoughtValue":{"min":0,"step":"0.1"},
- * "studyOverSoldValue":{"max":0,"step":"0.1"}
- * }
- * }
- */
-CIQ.Studies.studyLibrary = CIQ.Studies.studyLibrary || {};
-CIQ.extend(CIQ.Studies.studyLibrary, {
- ma: {
- name: "Moving Average",
- overlay: true,
- calculateFN: CIQ.Studies.calculateMovingAverage,
- inputs: { Period: 50, Field: "field", Type: "ma", Offset: 0 },
- outputs: { MA: "#FF0000" }
- },
- "STD Dev": {
- name: "Standard Deviation",
- calculateFN: CIQ.Studies.calculateStandardDeviation,
- inputs: {
- Period: 14,
- Field: "field",
- "Standard Deviations": 2,
- "Moving Average Type": "ma"
- },
- attributes: {
- "Standard Deviations": { min: 0.1, step: 0.1 }
- }
- },
- "True Range": {
- name: "True Range",
- calculateFN: CIQ.Studies.calculateStudyATR,
- inputs: {},
- outputs: { "True Range": "auto" }
- },
- volume: {
- name: "Volume Chart",
- range: "0 to max",
- yAxis: { ground: true, initialMarginTop: 0, zoom: 0 },
- seriesFN: CIQ.Studies.createVolumeChart,
- calculateFN: CIQ.Studies.calculateVolume,
- inputs: {},
- outputs: { "Up Volume": "#8cc176", "Down Volume": "#b82c0c" }
- }
-});
-
-};
-
-
-let __js_standard_symbolLookupBase_ = (_exports) => {
-
-/* global _CIQ, _timezoneJS, _SplinePlotter */
-
-
-var CIQ = typeof _CIQ !== "undefined" ? _CIQ : _exports.CIQ;
-
-if (!CIQ.ChartEngine.Driver) {
- console.error(
- "symbolLookupBase feature requires first activating quoteFeed feature."
- );
-} else {
- /**
- * Base class that drives the lookup (Symbol Search) functionality.
- *
- * You should derive your own Driver.Lookup that interacts with your datafeed.
- *
- * This is used with the [cq-lookup web component]{@link WebComponents.cq-lookup} and [CIQ.UI.Context.setLookupDriver](CIQ.UI.Context.html#setLookupDriver)
- *
- * @name CIQ.ChartEngine.Driver.Lookup
- * @constructor
- * @param {string[]} exchanges An array of exchanges that can be searched against
- * @example
- * // sample implementation
- * CIQ.ChartEngine.Driver.Lookup.ChartIQ=function(exchanges){
- * this.exchanges=exchanges;
- * if(!this.exchanges) this.exchanges=["XNYS","XASE","XNAS","XASX","INDCBSX","INDXASE","INDXNAS","IND_DJI","ARCX","INDARCX","forex"];
- * this.url="https://symbols.chartiq.com/chiq.symbolserver.SymbolLookup.service";
- * this.requestCounter=0; //used to invalidate old requests
- * };
- *
- * //Inherits all of the base Lookup Driver's properties via `CIQ.inheritsFrom()`
- * CIQ.inheritsFrom(CIQ.ChartEngine.Driver.Lookup.ChartIQ,CIQ.ChartEngine.Driver.Lookup);
- * @since 6.0.0
- */
- CIQ.ChartEngine.Driver.Lookup = function (exchanges) {};
-
- /**
- * **Abstract method** used to accept the selected text with optional filter and return an array of properly formatted objects.
- *
- * You should implement your own instance of this method to fetch results from your symbol list and return them by calling cb(your-results-array-here);
- *
- * Each element in the array should be of the following format:
- * {
- * display:["symbol-id","Symbol Description","exchange"],
- * data:{
- * symbol:"symbol-id",
- * name:"Symbol Description",
- * exchDis:"exchange"
- * }
- * }
- *
- * @param {string} text The text entered by the user
- * @param {string} [filter] The optional filter text selected by the user. This will be the innerHTML of the cq-filter element that is selected.
- * @param {number} maxResults Max number of results to return from the server
- * @param {function} cb Callback upon results
- * @memberof CIQ.ChartEngine.Driver.Lookup
- * @example
- // sample implementation
- CIQ.ChartEngine.Driver.Lookup.ChartIQ.prototype.acceptText=function(text, filter, maxResults, cb){
- if(filter=="FX") filter="FOREX";
- if(isNaN(parseInt(maxResults, 10))) maxResults=100;
- var url=this.url+"?t=" + encodeURIComponent(text) + "&m="+maxResults+"&x=[";
- if(this.exchanges){
- url+=this.exchanges.join(",");
- }
- url+="]";
- if(filter && filter.toUpperCase()!="ALL"){
- url+="&e=" + filter;
- }
-
- var counter=++this.requestCounter;
- var self=this;
- function handleResponse(status, response){
- if(counter
- * Although we do update this file periodically ,it may not be available immediately after every timezone change.
- * As such, if you require immediate updates, you should subscribe to a notification system that alerts you of these changes, and then adjust the file as needed.
- * www.iana.org/time-zones is a good source.
- *
- * The following code snippet demonstrates how to do this. (You can also just add synonyms this way as well).
- * In order to save space, you may want to cherry pick the zones that you will need, and then add them in your initialization code.
- * ```
- * var myAdditionalZones = {
- * "zones" : {
- * "America/Toronto": [
- * [ 300, "Canada", "E%sT", null ]
- * ]
- * },
- * "rules" : {
- * "Canada" : [
- * [ 2007, "max", "-", "Mar", "Sun>=8", [ 2, 0, 0, null ], 60, "D" ],
- * [ 2007, "max", "-", "Nov", "Sun>=1", [ 2, 0, 0, null ], 0, "S" ] ]
- * }
- * }
- *
- * // to add all timezones "zones" and "rules" you can simply load the entire timeZoneDataObject.txt file.
- * if(timezoneJS) timezoneJS.timezone.loadZoneDataFromObject(myAdditionalZones);
- * ```
- * Lastly, if you want users to be able to use the new timezones from the menus, be sure to also add the title for them to the `CIQ.timeZoneMap` object to keep the list and the settings in sync:
- * ```
- * CIQ.timeZoneMap["(UTC-05:00) Toronto"]="America/Toronto";
- * ```
- *
- * See {@link CIQ.ChartEngine#setTimeZone} for further instructions on how to set the different timezones on the chart.
- *
- * @type {object}
- * @memberof CIQ
- */
-CIQ.timeZoneMap = {
- "(UTC-11:00) American Samoa, Midway Island": "Pacific/Pago_Pago",
- "(UTC-10:00) Hawaii": "Pacific/Honolulu",
- "(UTC-09:00) Alaska": "America/Juneau",
- "(UTC-08:00) Pacific Time (US and Canada), Tijuana": "America/Los_Angeles",
- "(UTC-07:00) Arizona": "America/Phoenix",
- "(UTC-07:00) Chihuahua, Mazatlan": "America/Chihuahua",
- "(UTC-07:00) Mountain Time (US and Canada)": "America/Denver",
- "(UTC-06:00) Central America": "America/Costa_Rica",
- "(UTC-06:00) Central Time (US and Canada)": "America/Chicago",
- "(UTC-06:00) Guadalajara, Mexico City, Monterrey": "America/Mexico_City",
- "(UTC-06:00) Saskatchewan": "America/Regina",
- "(UTC-05:00) Bogota, Lima, Quito, Rio Branco": "America/Bogota",
- "(UTC-05:00) Eastern Time (US and Canada)": "America/New_York",
- "(UTC-05:00) Havana": "America/Havana",
- "(UTC-05:00) Port-au-Prince": "America/Port-au-Prince",
- "(UTC-04:00) Asuncion": "America/Asuncion",
- "(UTC-04:00) Santiago": "America/Santiago",
- "(UTC-04:00) Caracas": "America/Caracas",
- "(UTC-04:00) Atlantic Time (Canada)": "America/Halifax",
- "(UTC-04:00) Georgetown, La Paz, Manaus, San Juan": "America/Puerto_Rico",
- "(UTC-03:30) Newfoundland and Labrador": "America/St_Johns",
- "(UTC-03:00) Cancun, Jamaica, Panama": "America/Panama",
- "(UTC-03:00) Buenos Aires": "America/Argentina/Buenos_Aires",
- "(UTC-03:00) Punta Arenas": "America/Punta_Arenas",
- "(UTC-03:00) Montevideo": "America/Montevideo",
- "(UTC-03:00) Sao Paulo": "America/Sao_Paulo",
- "(UTC-02:00) Mid-Atlantic": "Atlantic/South_Georgia",
- "(UTC-01:00) Azores": "Atlantic/Azores",
- "(UTC-01:00) Cape Verde Islands": "Atlantic/Cape_Verde",
- "(UTC) Greenwich Mean Time, Reykjavik": "UTC",
- "(UTC) Dublin": "Europe/Dublin",
- "(UTC) Lisbon, London": "Europe/London",
- "(UTC+01:00) Algiers, Tunis": "Africa/Tunis",
- "(UTC+01:00) Casablanca": "Africa/Casablanca",
- "(UTC+01:00) Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna":
- "Europe/Amsterdam",
- "(UTC+01:00) Belgrade, Bratislava, Budapest, Ljubljana, Prague":
- "Europe/Belgrade",
- "(UTC+01:00) Brussels, Copenhagen, Madrid, Paris": "Europe/Brussels",
- "(UTC+01:00) Sarajevo, Skopje, Warsaw, Zagreb": "Europe/Sarajevo",
- "(UTC+02:00) Kaliningrad": "Europe/Kaliningrad",
- "(UTC+02:00) Athens, Bucharest": "Europe/Bucharest",
- "(UTC+02:00) Cairo": "Africa/Cairo",
- "(UTC+02:00) Harare, Johannesburg": "Africa/Johannesburg",
- "(UTC+02:00) Helsinki, Kiev, Riga, Sofia, Tallinn, Vilnius":
- "Europe/Helsinki",
- "(UTC+02:00) Cyprus": "Asia/Nicosia",
- "(UTC+02:00) Beirut": "Asia/Beirut",
- "(UTC+02:00) Damascus": "Asia/Damascus",
- "(UTC+02:00) Jerusalem": "Asia/Jerusalem",
- "(UTC+02:00) Amman": "Asia/Amman",
- "(UTC+03:00) Istanbul": "Europe/Istanbul",
- "(UTC+03:00) Baghdad, Kuwait, Qatar, Riyadh": "Asia/Riyadh",
- "(UTC+03:00) Minsk, Moscow, Kirov, Simferopol": "Europe/Moscow",
- "(UTC+03:00) Volgograd": "Europe/Volgograd",
- "(UTC+03:00) Nairobi": "Africa/Nairobi",
- "(UTC+03:30) Tehran": "Asia/Tehran",
- "(UTC+04:00) Baku": "Asia/Baku",
- "(UTC+04:00) Dubai, Muscat": "Asia/Dubai",
- "(UTC+04:00) Astrakhan, Samara, Saratov, Ulyanovsk": "Europe/Samara",
- "(UTC+04:30) Kabul": "Asia/Kabul",
- "(UTC+05:00) Karachi, Tashkent": "Asia/Karachi",
- "(UTC+05:00) Yekaterinburg": "Asia/Yekaterinburg",
- "(UTC+05:30) Chennai, Kolkata, Mumbai, New Delhi": "Asia/Kolkata",
- "(UTC+05:45) Kathmandu": "Asia/Kathmandu",
- "(UTC+06:00) Almaty": "Asia/Almaty",
- "(UTC+06:00) Omsk": "Asia/Omsk",
- "(UTC+06:00) Astana, Dhaka": "Asia/Dhaka",
- "(UTC+06:30) Yangon": "Asia/Yangon",
- "(UTC+07:00) Bangkok, Jakarta, Vietnam": "Asia/Bangkok",
- "(UTC+07:00) Hovd": "Asia/Hovd",
- "(UTC+07:00) Krasnoyarsk": "Asia/Krasnoyarsk",
- "(UTC+07:00) Novokuznetsk": "Asia/Novokuznetsk",
- "(UTC+07:00) Barnaul, Novosibirsk, Tomsk": "Asia/Novosibirsk",
- "(UTC+08:00) Beijing, Chongqing, Hong Kong SAR": "Asia/Hong_Kong",
- "(UTC+08:00) Brunei, Kuala Lumpur, Singapore": "Asia/Kuala_Lumpur",
- "(UTC+08:00) Irkutsk": "Asia/Irkutsk",
- "(UTC+08:00) Choibalsan, Ulaanbaatar": "Asia/Ulaanbaatar",
- "(UTC+08:00) Manila, Taipei": "Asia/Taipei",
- "(UTC+08:00) Perth": "Australia/Perth",
- "(UTC+08:45) Eucla": "Australia/Eucla",
- "(UTC+09:00) Osaka, Sapporo, Tokyo": "Asia/Tokyo",
- "(UTC+09:00) Pyongyang": "Asia/Pyongyang",
- "(UTC+09:00) Seoul": "Asia/Seoul",
- "(UTC+09:00) Chita, Khandyga, Yakutsk": "Asia/Yakutsk",
- "(UTC+09:30) Adelaide": "Australia/Adelaide",
- "(UTC+09:30) Darwin": "Australia/Darwin",
- "(UTC+10:00) Brisbane": "Australia/Brisbane",
- "(UTC+10:00) Canberra, Melbourne, Sydney": "Australia/Sydney",
- "(UTC+10:00) Guam, Port Moresby": "Pacific/Guam",
- "(UTC+10:00) Ust-Nera, Vladivostok": "Asia/Vladivostok",
- "(UTC+11:00) Noumea, Solomon Islands": "Pacific/Noumea",
- "(UTC+11:00) Magadan": "Asia/Magadan",
- "(UTC+11:00) Sakhalin, Srednekolymsk": "Asia/Srednekolymsk",
- "(UTC+12:00) Anadyr, Kamchatka": "Asia/Kamchatka",
- "(UTC+12:00) Auckland, Wellington": "Pacific/Auckland",
- "(UTC+12:00) Fiji": "Pacific/Fiji",
- "(UTC+12:45) Chatham": "Pacific/Chatham",
- "(UTC+13:00) Tonga": "Pacific/Tongatapu",
- "(UTC+13:00) Samoa": "Pacific/Apia",
- "(UTC+14:00) Kiritimati": "Pacific/Kiritimati"
-};
-
-// -----
-// The `timezoneJS.Date` object gives you full-blown timezone support, independent from the timezone set on the end-user's machine running the browser. It uses the Olson zoneinfo files for its timezone data.
-//
-// The constructor function and setter methods use proxy JavaScript Date objects behind the scenes, so you can use strings like '10/22/2006' with the constructor. You also get the same sensible wraparound behavior with numeric parameters (like setting a value of 14 for the month wraps around to the next March).
-//
-// The other significant difference from the built-in JavaScript Date is that `timezoneJS.Date` also has named properties that store the values of year, month, date, etc., so it can be directly serialized to JSON and used for data transfer.
-
-/*!
- * Copyright 2010 Matthew Eernisse (mde@fleegix.org)
- * and Open Source Applications Foundation
- *
- * Licensed under the Apache License, Version 2.0 (the 'License');
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an 'AS IS' BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * Credits: Ideas included from incomplete JS implementation of Olson
- * parser, 'XMLDAte' by Philippe Goetz (philippe.goetz@wanadoo.fr)
- *
- * Contributions:
- * Jan Niehusmann
- * Ricky Romero
- * Preston Hunt (prestonhunt@gmail.com)
- * Dov. B Katz (dov.katz@morganstanley.com)
- * Peter Bergström (pbergstr@mac.com)
- * Long Ho
- *
- * Modified from original by ChartIQ to include caching for improved performance
- */
-
-/*jshint laxcomma:true, laxbreak:true, expr:true, supernew:true*/
-(function () {
- // Standard initialization stuff to make sure the library is
- // usable on both client and server (node) side.
- "use strict";
- var _window = typeof window !== "undefined" ? window : null;
- var root = _window || (typeof global !== "undefined" ? global : {});
-
- timezoneJS.VERSION = "0.4.11";
-
- // Grab the ajax library from global context.
- // This can be jQuery, Zepto or fleegix.
- // You can also specify your own transport mechanism by declaring
- // `timezoneJS.timezone.transport` to a `function`. More details will follow
- var ajax_lib = root.$ || root.jQuery || root.Zepto,
- fleegix = root.fleegix,
- // Declare constant list of days and months. Unfortunately this doesn't leave room for i18n due to the Olson data being in English itself
- DAYS = (timezoneJS.Days = [
- "Sunday",
- "Monday",
- "Tuesday",
- "Wednesday",
- "Thursday",
- "Friday",
- "Saturday"
- ]),
- MONTHS = (timezoneJS.Months = [
- "January",
- "February",
- "March",
- "April",
- "May",
- "June",
- "July",
- "August",
- "September",
- "October",
- "November",
- "December"
- ]),
- SHORT_MONTHS = {},
- SHORT_DAYS = {},
- EXACT_DATE_TIME = {};
-
- //`{ 'Jan': 0, 'Feb': 1, 'Mar': 2, 'Apr': 3, 'May': 4, 'Jun': 5, 'Jul': 6, 'Aug': 7, 'Sep': 8, 'Oct': 9, 'Nov': 10, 'Dec': 11 }`
- for (var i = 0; i < MONTHS.length; i++) {
- SHORT_MONTHS[MONTHS[i].substr(0, 3)] = i;
- }
-
- //`{ 'Sun': 0, 'Mon': 1, 'Tue': 2, 'Wed': 3, 'Thu': 4, 'Fri': 5, 'Sat': 6 }`
- for (i = 0; i < DAYS.length; i++) {
- SHORT_DAYS[DAYS[i].substr(0, 3)] = i;
- }
-
- //Handle array indexOf in IE
- //From https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/indexOf
- //Extending Array prototype causes IE to iterate thru extra element
- var _arrIndexOf =
- Array.prototype.indexOf ||
- function (el) {
- if (this === null) {
- throw new TypeError();
- }
- var t = Object(this);
- var len = t.length >>> 0;
- if (len === 0) {
- return -1;
- }
- var n = 0;
- if (arguments.length > 1) {
- n = Number(arguments[1]);
- if (n != n) {
- // shortcut for verifying if it's NaN
- n = 0;
- } else if (n !== 0 && n !== Infinity && n !== -Infinity) {
- n = (n > 0 || -1) * Math.floor(Math.abs(n));
- }
- }
- if (n >= len) {
- return -1;
- }
- var k = n >= 0 ? n : Math.max(len - Math.abs(n), 0);
- for (; k < len; k++) {
- if (k in t && t[k] === el) {
- return k;
- }
- }
- return -1;
- };
-
- // Format a number to the length = digits. For ex:
- //
- // `_fixWidth(2, 2) = '02'`
- //
- // `_fixWidth(1998, 2) = '98'` // year, shorten it to the 2 digit representation
- //
- // `_fixWidth(23, 1) = '23'` // hour, even with 1 digit specified, do not trim
- //
- // This is used to pad numbers in converting date to string in ISO standard.
- var _fixWidth = function (number, digits) {
- if (typeof number !== "number") {
- throw "not a number: " + number;
- }
- var trim = number > 1000; // only trim 'year', as the others don't make sense why anyone would want that
- var s = number.toString();
- var s_len = s.length;
- if (trim && s_len > digits) {
- return s.substr(s_len - digits, s_len);
- }
- s = [s];
- while (s_len < digits) {
- s.unshift("0");
- s_len++;
- }
- return s.join("");
- };
-
- // Abstraction layer for different transport layers, including fleegix/jQuery/Zepto/Node.js
- //
- // Object `opts` include
- //
- // - `url`: url to ajax query
- //
- // - `async`: true for asynchronous, false otherwise. If false, return value will be response from URL. This is true by default
- //
- // - `success`: success callback function
- //
- // - `error`: error callback function
- // Returns response from URL if async is false, otherwise the AJAX request object itself
- var _transport = function (opts) {
- if (!opts) return;
- if (!opts.url) throw new Error("URL must be specified");
- if (!("async" in opts)) opts.async = true;
- // Client-side
- if (
- (!fleegix || typeof fleegix.xhr === "undefined") &&
- (!ajax_lib || typeof ajax_lib.ajax === "undefined")
- ) {
- throw new Error(
- "Please use the Fleegix.js XHR module, jQuery ajax, Zepto ajax, or define your own transport mechanism for downloading zone files."
- );
- }
- if (!opts.async) {
- return fleegix && fleegix.xhr
- ? fleegix.xhr.doReq({ url: opts.url, async: false })
- : ajax_lib.ajax({ url: opts.url, async: false, dataType: "text" })
- .responseText;
- }
- return fleegix && fleegix.xhr
- ? fleegix.xhr.send({
- url: opts.url,
- method: "get",
- handleSuccess: opts.success,
- handleErr: opts.error
- })
- : ajax_lib.ajax({
- url: opts.url,
- dataType: "text",
- method: "GET",
- error: opts.error,
- success: opts.success
- });
- };
-
- timezoneJS.ruleCache = {};
-
- // Constructor, which is similar to that of the native Date object itself
- timezoneJS.Date = function () {
- if (this === timezoneJS) {
- throw "timezoneJS.Date object must be constructed with 'new'";
- }
- var args = Array.prototype.slice.apply(arguments),
- dt = null,
- tz = null,
- arr = [],
- valid = false;
- //We support several different constructors, including all the ones from `Date` object
- // with a timezone string at the end.
- //
- //- `[tz]`: Returns object with time in `tz` specified.
- //
- // - `utcMillis`, `[tz]`: Return object with UTC time = `utcMillis`, in `tz`.
- //
- // - `Date`, `[tz]`: Returns object with UTC time = `Date.getTime()`, in `tz`.
- //
- // - `year, month, [date,] [hours,] [minutes,] [seconds,] [millis,] [tz]: Same as `Date` object
- // with tz.
- //
- // - `Array`: Can be any combo of the above.
- //
- //If 1st argument is an array, we can use it as a list of arguments itself
- if (Object.prototype.toString.call(args[0]) === "[object Array]") {
- args = args[0];
- }
- // If the last string argument doesn't parse as a Date, treat it as tz
- if (typeof args[args.length - 1] === "string") {
- valid = Date.parse(args[args.length - 1].replace(/GMT[+-]\d+/, ""));
- if (isNaN(valid) || valid === null) {
- // Checking against null is required for compatability with Datejs
- tz = args.pop();
- }
- }
- // Old code: still need it?
- //if (typeof args[args.length - 1] === 'string' /*&& isNaN(Date.parse(args[args.length - 1].replace(/GMT\+\d+/, '')))*/) { // This was causing any timezone with GMT to stop working as in "Etc/GMT-7"
- // tz = args.pop();
- //}
- var is_dt_local = false;
- switch (args.length) {
- case 0:
- dt = new Date();
- break;
- case 1:
- dt = new Date(args[0]);
- // Date strings are local if they do not contain 'Z', 'T' or timezone offsets like '+0200'
- // - more info below
- if (
- typeof args[0] == "string" &&
- args[0].search(/[+-][0-9]{4}/) == -1 &&
- args[0].search(/Z/) == -1 &&
- args[0].search(/T/) == -1
- ) {
- is_dt_local = true;
- }
- break;
- case 2:
- dt = new Date(args[0], args[1]);
- is_dt_local = true;
- break;
- default:
- for (var i = 0; i < 7; i++) {
- arr[i] = args[i] || 0;
- }
- dt = new Date(arr[0], arr[1], arr[2], arr[3], arr[4], arr[5], arr[6]);
- is_dt_local = true;
- break;
- }
-
- if (isNaN(dt.getTime())) {
- // invalid date were passed
- throw new Error("Invalid date");
- }
-
- this._useCache = false;
- this._tzInfo = {};
- this._day = 0;
- this.year = 0;
- this.month = 0;
- this.date = 0;
- this.hours = 0;
- this.minutes = 0;
- this.seconds = 0;
- this.milliseconds = 0;
- this.timezone = tz || null;
- // Tricky part:
- // The date is either given as unambiguous UTC date or otherwise the date is assumed
- // to be a date in timezone `tz` or a locale date if `tz` is not provided. Thus, to
- // determine how to use `dt` we distinguish between the following cases:
- // - UTC (is_dt_local = false)
- // `timezoneJS.Date(millis, [tz])`
- // `timezoneJS.Date(Date, [tz])`
- // `timezoneJS.Date(dt_str_tz, [tz])`
- // - local/timezone `tz` (is_dt_local = true)
- // `timezoneJS.Date(year, mon, day, [hour], [min], [second], [tz])`
- // `timezoneJS.Date(dt_str, [tz])`
- //
- // `dt_str_tz` is a date string containing timezone information, i.e. containing 'Z', 'T' or
- // /[+-][0-9]{4}/ (e.g. '+0200'), while `dt_str` is a string which does not contain
- // timezone information. See: http://dygraphs.com/date-formats.html
- if (is_dt_local) {
- this.setFromDateObjProxy(dt);
- } else {
- this.setFromTimeProxy(dt.getTime(), tz);
- }
- };
-
- // Implements most of the native Date object
- CIQ.extend(
- timezoneJS.Date.prototype,
- {
- getDate: function () {
- return this.date;
- },
- getDay: function () {
- return this._day;
- },
- getFullYear: function () {
- return this.year;
- },
- getMonth: function () {
- return this.month;
- },
- getYear: function () {
- return this.year - 1900;
- },
- getHours: function () {
- return this.hours;
- },
- getMilliseconds: function () {
- return this.milliseconds;
- },
- getMinutes: function () {
- return this.minutes;
- },
- getSeconds: function () {
- return this.seconds;
- },
- getUTCDate: function () {
- return this.getUTCDateProxy().getUTCDate();
- },
- getUTCDay: function () {
- return this.getUTCDateProxy().getUTCDay();
- },
- getUTCFullYear: function () {
- return this.getUTCDateProxy().getUTCFullYear();
- },
- getUTCHours: function () {
- return this.getUTCDateProxy().getUTCHours();
- },
- getUTCMilliseconds: function () {
- return this.getUTCDateProxy().getUTCMilliseconds();
- },
- getUTCMinutes: function () {
- return this.getUTCDateProxy().getUTCMinutes();
- },
- getUTCMonth: function () {
- return this.getUTCDateProxy().getUTCMonth();
- },
- getUTCSeconds: function () {
- return this.getUTCDateProxy().getUTCSeconds();
- },
- // Time adjusted to user-specified timezone
- getTime: function () {
- return this._timeProxy + this.getTimezoneOffset() * 60 * 1000;
- },
- getTimezone: function () {
- return this.timezone;
- },
- getTimezoneOffset: function () {
- return this.getTimezoneInfo().tzOffset;
- },
- getTimezoneAbbreviation: function () {
- return this.getTimezoneInfo().tzAbbr;
- },
- getTimezoneInfo: function () {
- if (this._useCache) return this._tzInfo;
- var res;
- // If timezone is specified, get the correct timezone info based on the Date given
- if (this.timezone) {
- res =
- this.timezone === "Etc/UTC" || this.timezone === "Etc/GMT"
- ? { tzOffset: 0, tzAbbr: "UTC" }
- : timezoneJS.timezone.getTzInfo(this._timeProxy, this.timezone);
- }
- // If no timezone was specified, use the local browser offset
- else {
- res = { tzOffset: this.getLocalOffset(), tzAbbr: null };
- }
- this._tzInfo = res;
- this._useCache = true;
- return res;
- },
- getUTCDateProxy: function () {
- var dt = new Date(this._timeProxy);
- dt.setUTCMinutes(dt.getUTCMinutes() + this.getTimezoneOffset());
- return dt;
- },
- setDate: function (date) {
- this.setAttribute("date", date);
- return this.getTime();
- },
- setFullYear: function (year, month, date) {
- if (date !== undefined) {
- this.setAttribute("date", 1);
- }
- this.setAttribute("year", year);
- if (month !== undefined) {
- this.setAttribute("month", month);
- }
- if (date !== undefined) {
- this.setAttribute("date", date);
- }
- return this.getTime();
- },
- setMonth: function (month, date) {
- this.setAttribute("month", month);
- if (date !== undefined) {
- this.setAttribute("date", date);
- }
- return this.getTime();
- },
- setYear: function (year) {
- year = Number(year);
- if (0 <= year && year <= 99) {
- year += 1900;
- }
- this.setUTCAttribute("year", year);
- return this.getTime();
- },
- setHours: function (hours, minutes, seconds, milliseconds) {
- this.setAttribute("hours", hours);
- if (minutes !== undefined) {
- this.setAttribute("minutes", minutes);
- }
- if (seconds !== undefined) {
- this.setAttribute("seconds", seconds);
- }
- if (milliseconds !== undefined) {
- this.setAttribute("milliseconds", milliseconds);
- }
- return this.getTime();
- },
- setMinutes: function (minutes, seconds, milliseconds) {
- this.setAttribute("minutes", minutes);
- if (seconds !== undefined) {
- this.setAttribute("seconds", seconds);
- }
- if (milliseconds !== undefined) {
- this.setAttribute("milliseconds", milliseconds);
- }
- return this.getTime();
- },
- setSeconds: function (seconds, milliseconds) {
- this.setAttribute("seconds", seconds);
- if (milliseconds !== undefined) {
- this.setAttribute("milliseconds", milliseconds);
- }
- return this.getTime();
- },
- setMilliseconds: function (milliseconds) {
- this.setAttribute("milliseconds", milliseconds);
- return this.getTime();
- },
- setTime: function (n) {
- if (isNaN(n)) {
- throw new Error("Units must be a number.");
- }
- this.setFromTimeProxy(n, this.timezone);
- return this.getTime();
- },
- setUTCFullYear: function (year, month, date) {
- if (date !== undefined) {
- this.setUTCAttribute("date", 1);
- }
- this.setUTCAttribute("year", year);
- if (month !== undefined) {
- this.setUTCAttribute("month", month);
- }
- if (date !== undefined) {
- this.setUTCAttribute("date", date);
- }
- return this.getTime();
- },
- setUTCMonth: function (month, date) {
- this.setUTCAttribute("month", month);
- if (date !== undefined) {
- this.setUTCAttribute("date", date);
- }
- return this.getTime();
- },
- setUTCDate: function (date) {
- this.setUTCAttribute("date", date);
- return this.getTime();
- },
- setUTCHours: function (hours, minutes, seconds, milliseconds) {
- this.setUTCAttribute("hours", hours);
- if (minutes !== undefined) {
- this.setUTCAttribute("minutes", minutes);
- }
- if (seconds !== undefined) {
- this.setUTCAttribute("seconds", seconds);
- }
- if (milliseconds !== undefined) {
- this.setUTCAttribute("milliseconds", milliseconds);
- }
- return this.getTime();
- },
- setUTCMinutes: function (minutes, seconds, milliseconds) {
- this.setUTCAttribute("minutes", minutes);
- if (seconds !== undefined) {
- this.setUTCAttribute("seconds", seconds);
- }
- if (milliseconds !== undefined) {
- this.setUTCAttribute("milliseconds", milliseconds);
- }
- return this.getTime();
- },
- setUTCSeconds: function (seconds, milliseconds) {
- this.setUTCAttribute("seconds", seconds);
- if (milliseconds !== undefined) {
- this.setUTCAttribute("milliseconds", milliseconds);
- }
- return this.getTime();
- },
- setUTCMilliseconds: function (milliseconds) {
- this.setUTCAttribute("milliseconds", milliseconds);
- return this.getTime();
- },
- setFromDateObjProxy: function (dt) {
- this.year = dt.getFullYear();
- this.month = dt.getMonth();
- this.date = dt.getDate();
- this.hours = dt.getHours();
- this.minutes = dt.getMinutes();
- this.seconds = dt.getSeconds();
- this.milliseconds = dt.getMilliseconds();
- this._day = dt.getDay();
- this._dateProxy = dt;
- this._timeProxy = Date.UTC(
- this.year,
- this.month,
- this.date,
- this.hours,
- this.minutes,
- this.seconds,
- this.milliseconds
- );
- this._useCache = false;
- },
- setFromTimeProxy: function (utcMillis, tz) {
- var dt = new Date(utcMillis);
- var tzOffset = tz
- ? timezoneJS.timezone.getTzInfo(utcMillis, tz, true).tzOffset
- : dt.getTimezoneOffset();
- dt.setTime(utcMillis + (dt.getTimezoneOffset() - tzOffset) * 60000);
- this.setFromDateObjProxy(dt);
- },
- setAttribute: function (unit, n) {
- if (isNaN(n)) {
- throw new Error("Units must be a number.");
- }
- var dt = this._dateProxy;
- var meth =
- unit === "year"
- ? "FullYear"
- : unit.substr(0, 1).toUpperCase() + unit.substr(1);
- dt["set" + meth](n);
- this.setFromDateObjProxy(dt);
- },
- setUTCAttribute: function (unit, n) {
- if (isNaN(n)) {
- throw new Error("Units must be a number.");
- }
- var meth =
- unit === "year"
- ? "FullYear"
- : unit.substr(0, 1).toUpperCase() + unit.substr(1);
- var dt = this.getUTCDateProxy();
- dt["setUTC" + meth](n);
- dt.setUTCMinutes(dt.getUTCMinutes() - this.getTimezoneOffset());
- this.setFromTimeProxy(
- dt.getTime() + this.getTimezoneOffset() * 60000,
- this.timezone
- );
- },
- setTimezone: function (tz) {
- var previousOffset = this.getTimezoneInfo().tzOffset;
- this.timezone = tz;
- this._useCache = false;
- // Set UTC minutes offsets by the delta of the two timezones
- this.setUTCMinutes(
- this.getUTCMinutes() -
- this.getTimezoneInfo().tzOffset +
- previousOffset
- );
- },
- removeTimezone: function () {
- this.timezone = null;
- this._useCache = false;
- },
- valueOf: function () {
- return this.getTime();
- },
- clone: function () {
- return this.timezone
- ? new timezoneJS.Date(this.getTime(), this.timezone)
- : new timezoneJS.Date(this.getTime());
- },
- toGMTString: function () {
- return this.toString("EEE, dd MMM yyyy HH:mm:ss Z", "Etc/GMT");
- },
- toLocaleStringIntl: function () {},
- toLocaleDateString: function () {},
- toLocaleTimeString: function () {},
- toSource: function () {},
- toISOString: function () {
- return this.toString("yyyy-MM-ddTHH:mm:ss.SSS", "Etc/UTC") + "Z";
- },
- toJSON: function () {
- return this.toISOString();
- },
- toDateString: function () {
- return this.toString("EEE MMM dd yyyy");
- },
- toTimeString: function () {
- return this.toString("H:mm k");
- },
- // Allows different format following ISO8601 format:
- toString: function (format, tz) {
- // Default format is the same as toISOString
- if (!format) format = "yyyy-MM-ddTHH:mm:ss.SSS";
- var result = format;
- var tzInfo = tz
- ? timezoneJS.timezone.getTzInfo(this.getTime(), tz)
- : this.getTimezoneInfo();
- var _this = this;
- // If timezone is specified, get a clone of the current Date object and modify it
- if (tz) {
- _this = this.clone();
- _this.setTimezone(tz);
- }
- var hours = _this.getHours();
- return (
- result
- // fix the same characters in Month names
- .replace(/a+/g, function () {
- return "k";
- })
- // `y`: year
- .replace(/y+/g, function (token) {
- return _fixWidth(_this.getFullYear(), token.length);
- })
- // `d`: date
- .replace(/d+/g, function (token) {
- return _fixWidth(_this.getDate(), token.length);
- })
- // `m`: minute
- .replace(/m+/g, function (token) {
- return _fixWidth(_this.getMinutes(), token.length);
- })
- // `s`: second
- .replace(/s+/g, function (token) {
- return _fixWidth(_this.getSeconds(), token.length);
- })
- // `S`: millisecond
- .replace(/S+/g, function (token) {
- return _fixWidth(_this.getMilliseconds(), token.length);
- })
- // 'h': 12 hour format
- .replace(/h+/g, function (token) {
- return _fixWidth(
- hours % 12 === 0 ? 12 : hours % 12,
- token.length
- );
- })
- // `M`: month. Note: `MM` will be the numeric representation (e.g February is 02) but `MMM` will be text representation (e.g February is Feb)
- .replace(/M+/g, function (token) {
- var _month = _this.getMonth(),
- _len = token.length;
- if (_len > 3) {
- return timezoneJS.Months[_month];
- } else if (_len > 2) {
- return timezoneJS.Months[_month].substring(0, _len);
- }
- return _fixWidth(_month + 1, _len);
- })
- // `k`: AM/PM
- .replace(/k+/g, function () {
- if (hours >= 12) {
- if (hours > 12) {
- hours -= 12;
- }
- return "PM";
- }
- return "AM";
- })
- // `H`: hour
- .replace(/H+/g, function (token) {
- return _fixWidth(hours, token.length);
- })
- // `E`: day
- .replace(/E+/g, function (token) {
- return DAYS[_this.getDay()].substring(0, token.length);
- })
- // `Z`: timezone abbreviation
- .replace(/Z+/gi, function () {
- return tzInfo.tzAbbr;
- })
- );
- },
- toUTCString: function () {
- return this.toGMTString();
- },
- civilToJulianDayNumber: function (y, m, d) {
- var a;
- // Adjust for zero-based JS-style array
- m++;
- if (m > 12) {
- a = parseInt(m / 12, 10);
- m = m % 12;
- y += a;
- }
- if (m <= 2) {
- y -= 1;
- m += 12;
- }
- a = Math.floor(y / 100);
- var b = 2 - a + Math.floor(a / 4),
- jDt =
- Math.floor(365.25 * (y + 4716)) +
- Math.floor(30.6001 * (m + 1)) +
- d +
- b -
- 1524;
- return jDt;
- },
- getLocalOffset: function () {
- return this._dateProxy.getTimezoneOffset();
- }
- },
- true
- );
-
- timezoneJS.timezone = new (function () {
- var _this = this,
- regionMap = {
- Etc: "etcetera",
- EST: "northamerica",
- MST: "northamerica",
- HST: "northamerica",
- EST5EDT: "northamerica",
- CST6CDT: "northamerica",
- MST7MDT: "northamerica",
- PST8PDT: "northamerica",
- America: ["northamerica", "southamerica"],
- Pacific: "australasia",
- Atlantic: "europe",
- Africa: "africa",
- Indian: "africa",
- Antarctica: "antarctica",
- Asia: "asia",
- Australia: "australasia",
- Europe: "europe",
- WET: "europe",
- CET: "europe",
- MET: "europe",
- EET: "europe"
- },
- regionExceptions = {
- "Pacific/Honolulu": "northamerica",
- "Atlantic/Bermuda": "northamerica",
- "Atlantic/Cape_Verde": "africa",
- "Atlantic/St_Helena": "africa",
- "Indian/Kerguelen": "antarctica",
- "Indian/Chagos": "asia",
- "Indian/Maldives": "asia",
- "Indian/Christmas": "australasia",
- "Indian/Cocos": "australasia",
- "America/Danmarkshavn": "europe",
- "America/Scoresbysund": "europe",
- "America/Godthab": "europe",
- "America/Thule": "europe",
- "Asia/Istanbul": "europe",
- "Asia/Yekaterinburg": "europe",
- "Asia/Omsk": "europe",
- "Asia/Novosibirsk": "europe",
- "Asia/Krasnoyarsk": "europe",
- "Asia/Irkutsk": "europe",
- "Asia/Yakutsk": "europe",
- "Asia/Vladivostok": "europe",
- "Asia/Sakhalin": "europe",
- "Asia/Magadan": "europe",
- "Asia/Kamchatka": "europe",
- "Asia/Anadyr": "europe",
- "Africa/Ceuta": "europe",
- GMT: "etcetera",
- "Europe/Nicosia": "asia"
- };
- function invalidTZError(t) {
- throw new Error(
- "Timezone '" +
- t +
- "' is either incorrect, or not loaded in the timezone registry."
- );
- }
- function builtInLoadZoneFile(fileName, opts) {
- var url = _this.zoneFileBasePath + "/" + fileName;
- return !opts || !opts.async
- ? _this.parseZones(_this.transport({ url: url, async: false }))
- : _this.transport({
- async: true,
- url: url,
- success: function (str) {
- return (
- _this.parseZones(str) &&
- typeof opts.callback === "function" &&
- opts.callback()
- );
- },
- error: function () {
- throw new Error("Error retrieving '" + url + "' zoneinfo files");
- }
- });
- }
- function getRegionForTimezone(tz) {
- var exc = regionExceptions[tz],
- reg,
- ret;
- if (exc) return exc;
- reg = tz.split("/")[0];
- ret = regionMap[reg];
- // If there's nothing listed in the main regions for this TZ, check the 'backward' links
- if (ret) return ret;
- var link = _this.zones[tz];
- if (typeof link === "string") {
- return getRegionForTimezone(link);
- }
- // Backward-compat file hasn't loaded yet, try looking in there
- if (!_this.loadedZones.backward) {
- // This is for obvious legacy zones (e.g., Iceland) that don't even have a prefix like 'America/' that look like normal zones
- _this.loadZoneFile("backward");
- return getRegionForTimezone(tz);
- }
- invalidTZError(tz);
- }
- //str has format hh:mm, can be negative
- function parseTimeString(str) {
- var pat = /(\d+)(?::0*(\d*))?(?::0*(\d*))?([wsugz])?$/;
- var hms = str.match(pat);
- hms[1] = parseInt(hms[1], 10);
- hms[2] = hms[2] ? parseInt(hms[2], 10) : 0;
- hms[3] = hms[3] ? parseInt(hms[3], 10) : 0;
- return hms.slice(1, 5);
- }
- //z is something like `[ '-3:44:40', '-', 'LMT', '1911', 'May', '15', '' ]` or `[ '-5:00', '-', 'EST', '1974', 'Apr', '28', '2:00' ]`
- function processZone(z) {
- if (!z[3]) {
- return;
- }
- var yea = parseInt(z[3], 10),
- mon = 11,
- dat = 31;
- //If month is there
- if (z[4]) {
- mon = SHORT_MONTHS[z[4].substr(0, 3)];
- dat = parseInt(z[5], 10) || 1;
- }
- var t = z[6] ? parseTimeString(z[6]) : [0, 0, 0];
- return [yea, mon, dat, t[0], t[1], t[2]];
- }
- function getZone(dt, tz) {
- var utcMillis = typeof dt === "number" ? dt : new Date(+dt).getTime();
- var t = tz;
- var zoneList = _this.zones[t];
- // Follow links to get to an actual zone
- while (typeof zoneList === "string") {
- t = zoneList;
- zoneList = _this.zones[t];
- }
- if (!zoneList) {
- // Backward-compat file hasn't loaded yet, try looking in there
- if (!_this.loadedZones.backward) {
- //This is for backward entries like 'America/Fort_Wayne' that
- // getRegionForTimezone *thinks* it has a region file and zone
- // for (e.g., America => 'northamerica'), but in reality it's a
- // legacy zone we need the backward file for.
- _this.loadZoneFile("backward");
- return getZone(dt, tz);
- } else if (t && t !== tz) {
- //Load the linked zone found in the backward file
- _this.lazyLoadZoneFiles(t);
- return getZone(dt, t);
- }
- invalidTZError(t);
- }
- if (zoneList.length === 0) {
- throw new Error("No Zone found for '" + tz + "' on " + dt);
- }
- //Do backwards lookup since most use cases deal with newer dates.
- for (var i = zoneList.length - 1; i >= 0; i--) {
- var z = zoneList[i];
- if (z[3] && utcMillis > z[3]) break;
- }
- return zoneList[i + 1];
- }
- function getBasicOffset(time) {
- var off = parseTimeString(time),
- adj = time.charAt(0) === "-" ? -1 : 1;
- off = adj * (((off[0] * 60 + off[1]) * 60 + off[2]) * 1000);
- return off / 60 / 1000;
- }
- function getAdjustedOffset(off, min) {
- return -Math.ceil(min - off);
- }
-
- //if isUTC is true, date is given in UTC, otherwise it's given
- // in local time (ie. date.getUTC*() returns local time components)
- function getRule(dt, zone, isUTC, cacheKey) {
- var date = typeof dt === "number" ? new Date(dt) : dt;
- var ruleset = zone[1];
- var basicOffset = zone[0];
-
- // If the zone has a DST rule like '1:00', create a rule and return it
- // instead of looking it up in the parsed rules
- var staticDstMatch = ruleset.match(/^([0-9]):([0-9][0-9])$/);
- if (staticDstMatch) {
- return [
- -1000000,
- "max",
- "-",
- "Jan",
- 1,
- [0, 0, 0],
- parseInt(staticDstMatch[1], 10) * 60 +
- parseInt(staticDstMatch[2], 10),
- "-"
- ];
- }
-
- //Convert a date to UTC. Depending on the 'type' parameter, the date
- // parameter may be:
- //
- // - `u`, `g`, `z`: already UTC (no adjustment).
- //
- // - `s`: standard time (adjust for time zone offset but not for DST)
- //
- // - `w`: wall clock time (adjust for both time zone and DST offset).
- //
- // DST adjustment is done using the rule given as third argument.
- var convertDateToUTC = function (date, type, rule) {
- var offset = 0;
-
- if (type === "u" || type === "g" || type === "z") {
- // UTC
- offset = 0;
- } else if (type === "s") {
- // Standard Time
- offset = basicOffset;
- } else if (type === "w" || !type) {
- // Wall Clock Time
- offset = getAdjustedOffset(basicOffset, rule[6]);
- } else {
- throw new Error("unknown type " + type);
- }
- offset *= 60 * 1000; // to millis
-
- return new Date(date.getTime() + offset);
- };
-
- //Step 1: Find applicable rules for this year.
- //
- //Step 2: Sort the rules by effective date.
- //
- //Step 3: Check requested date to see if a rule has yet taken effect this year. If not,
- //
- //Step 4: Get the rules for the previous year. If there isn't an applicable rule for last year, then
- // there probably is no current time offset since they seem to explicitly turn off the offset
- // when someone stops observing DST.
- //
- // FIXME if this is not the case and we'll walk all the way back (ugh).
- //
- //Step 5: Sort the rules by effective date.
- //Step 6: Apply the most recent rule before the current time.
- var convertRuleToExactDateAndTime = function (yearAndRule, prevRule) {
- var year = yearAndRule[0],
- rule = yearAndRule[1];
- // Assume that the rule applies to the year of the given date.
-
- var hms = rule[5];
- var effectiveDate;
-
- if (!EXACT_DATE_TIME[year]) EXACT_DATE_TIME[year] = {};
-
- // Result for given parameters is already stored
- if (EXACT_DATE_TIME[year][rule])
- effectiveDate = EXACT_DATE_TIME[year][rule];
- else {
- //If we have a specific date, use that!
- if (!isNaN(rule[4])) {
- effectiveDate = new Date(
- Date.UTC(
- year,
- SHORT_MONTHS[rule[3]],
- rule[4],
- hms[0],
- hms[1],
- hms[2],
- 0
- )
- );
- }
- //Let's hunt for the date.
- else {
- var targetDay, operator;
- //Example: `lastThu`
- if (rule[4].substr(0, 4) === "last") {
- // Start at the last day of the month and work backward.
- effectiveDate = new Date(
- Date.UTC(
- year,
- SHORT_MONTHS[rule[3]] + 1,
- 1,
- hms[0] - 24,
- hms[1],
- hms[2],
- 0
- )
- );
- targetDay = SHORT_DAYS[rule[4].substr(4, 3)];
- operator = "<=";
- }
- //Example: `Sun>=15`
- else {
- //Start at the specified date.
- effectiveDate = new Date(
- Date.UTC(
- year,
- SHORT_MONTHS[rule[3]],
- rule[4].substr(5),
- hms[0],
- hms[1],
- hms[2],
- 0
- )
- );
- targetDay = SHORT_DAYS[rule[4].substr(0, 3)];
- operator = rule[4].substr(3, 2);
- }
- var ourDay = effectiveDate.getUTCDay();
- //Go forwards.
- if (operator === ">=") {
- effectiveDate.setUTCDate(
- effectiveDate.getUTCDate() +
- (targetDay - ourDay + (targetDay < ourDay ? 7 : 0))
- );
- }
- //Go backwards. Looking for the last of a certain day, or operator is '<=' (less likely).
- else {
- effectiveDate.setUTCDate(
- effectiveDate.getUTCDate() +
- (targetDay - ourDay - (targetDay > ourDay ? 7 : 0))
- );
- }
- }
- EXACT_DATE_TIME[year][rule] = effectiveDate;
- }
-
- //If previous rule is given, correct for the fact that the starting time of the current
- // rule may be specified in local time.
- if (prevRule) {
- effectiveDate = convertDateToUTC(effectiveDate, hms[3], prevRule);
- }
- return effectiveDate;
- };
-
- var findApplicableRules = function (year, ruleset) {
- var applicableRules = [];
- for (var i = 0; ruleset && i < ruleset.length; i++) {
- //Exclude future rules.
- if (
- ruleset[i][0] <= year &&
- // Date is in a set range.
- (ruleset[i][1] >= year ||
- // Date is in an 'only' year.
- (ruleset[i][0] === year && ruleset[i][1] === "only") ||
- //We're in a range from the start year to infinity.
- ruleset[i][1] === "max")
- ) {
- //It's completely okay to have any number of matches here.
- // Normally we should only see two, but that doesn't preclude other numbers of matches.
- // These matches are applicable to this year.
- applicableRules.push([year, ruleset[i]]);
- }
- }
- return applicableRules;
- };
-
- var compareDates = function (a, b, prev) {
- var year, rule;
- if (!(a instanceof Date)) {
- year = a[0];
- rule = a[1];
- a =
- !prev && EXACT_DATE_TIME[year] && EXACT_DATE_TIME[year][rule]
- ? EXACT_DATE_TIME[year][rule]
- : convertRuleToExactDateAndTime(a, prev);
- } else if (prev) {
- a = convertDateToUTC(a, isUTC ? "u" : "w", prev);
- }
- if (!(b instanceof Date)) {
- year = b[0];
- rule = b[1];
- b =
- !prev && EXACT_DATE_TIME[year] && EXACT_DATE_TIME[year][rule]
- ? EXACT_DATE_TIME[year][rule]
- : convertRuleToExactDateAndTime(b, prev);
- } else if (prev) {
- b = convertDateToUTC(b, isUTC ? "u" : "w", prev);
- }
- a = Number(a);
- b = Number(b);
- return a - b;
- };
-
- var year = date.getUTCFullYear();
- var applicableRules;
-
- var cache = timezoneJS.ruleCache[cacheKey];
- if (!cache) cache = timezoneJS.ruleCache[cacheKey] = {};
- applicableRules = cache[year];
- if (!applicableRules) {
- applicableRules = findApplicableRules(year - 1, _this.rules[ruleset]);
- applicableRules = applicableRules.concat(
- findApplicableRules(year, _this.rules[ruleset])
- );
- applicableRules.sort(compareDates); // Probably already sorted?
- cache[year] = applicableRules;
- }
-
- if (!applicableRules || !applicableRules.length) return null; // No applicable rules
-
- var prev;
- for (var i = applicableRules.length - 1; i >= 0; i--) {
- if (i > 0) prev = applicableRules[i - 1][1];
- else prev = null;
- var rule = applicableRules[i];
- if (!rule[2]) {
- rule[2] = convertRuleToExactDateAndTime(rule, prev); // cache the exactDateAndTime, this saves a lot of cycles!
- }
- if (compareDates(date, rule, prev) >= 0) return rule[1];
- }
- return null;
-
- /*
- applicableRules = findApplicableRules(year, _this.rules[ruleset]);
- applicableRules.push(date);
- //While sorting, the time zone in which the rule starting time is specified
- // is ignored. This is ok as long as the timespan between two DST changes is
- // larger than the DST offset, which is probably always true.
- // As the given date may indeed be close to a DST change, it may get sorted
- // to a wrong position (off by one), which is corrected below.
- applicableRules.sort(compareDates);
-
- //If there are not enough past DST rules...
- if (_arrIndexOf.call(applicableRules, date) < 2) {
- applicableRules = applicableRules.concat(findApplicableRules(year-1, _this.rules[ruleset]));
- applicableRules.sort(compareDates);
- }
- var pinpoint = _arrIndexOf.call(applicableRules, date);
- if (pinpoint > 1 && compareDates(date, applicableRules[pinpoint-1], applicableRules[pinpoint-2][1]) < 0) {
- //The previous rule does not really apply, take the one before that.
- return applicableRules[pinpoint - 2][1];
- } else if (pinpoint > 0 && pinpoint < applicableRules.length - 1 && compareDates(date, applicableRules[pinpoint+1], applicableRules[pinpoint-1][1]) > 0) {
-
- //The next rule does already apply, take that one.
- return applicableRules[pinpoint + 1][1];
- } else if (pinpoint === 0) {
- //No applicable rule found in this and in previous year.
- return null;
- }
- return applicableRules[pinpoint - 1][1];
- */
- }
- function getAbbreviation(zone, rule) {
- var base = zone[2];
- if (base.indexOf("%s") > -1) {
- var repl;
- if (rule) {
- repl = rule[7] === "-" ? "" : rule[7];
- }
- //FIXME: Right now just falling back to Standard --
- // apparently ought to use the last valid rule,
- // although in practice that always ought to be Standard
- else {
- repl = "S";
- }
- return base.replace("%s", repl);
- } else if (base.indexOf("/") > -1) {
- //Chose one of two alternative strings.
- return base.split("/", 2)[rule ? (rule[6] ? 1 : 0) : 0];
- }
- return base;
- }
-
- this.zoneFileBasePath = null;
- this.zoneFiles = [
- "africa",
- "antarctica",
- "asia",
- "australasia",
- "backward",
- "etcetera",
- "europe",
- "northamerica",
- "pacificnew",
- "southamerica"
- ];
- this.loadingSchemes = {
- PRELOAD_ALL: "preloadAll",
- LAZY_LOAD: "lazyLoad",
- MANUAL_LOAD: "manualLoad"
- };
- this.getRegionForTimezone = getRegionForTimezone;
- this.loadingScheme = this.loadingSchemes.LAZY_LOAD;
- this.loadedZones = {};
- this.zones = {};
- this.rules = {};
-
- this.init = function (o) {
- var opts = { async: true },
- def =
- this.loadingScheme === this.loadingSchemes.PRELOAD_ALL
- ? this.zoneFiles
- : this.defaultZoneFile || "northamerica";
- //Override default with any passed-in opts
- for (var p in o) {
- opts[p] = o[p];
- }
- return this.loadZoneFiles(def, opts);
- };
-
- //Get a single zone file, or all files in an array
- this.loadZoneFiles = function (fileNames, opts) {
- var callbackFn,
- done = 0;
- if (typeof fileNames === "string") {
- return this.loadZoneFile(fileNames, opts);
- }
- //Wraps callback function in another one that makes
- // sure all files have been loaded.
- opts = opts || {};
- callbackFn = opts.callback;
- opts.callback = function () {
- done++;
- done === fileNames.length &&
- typeof callbackFn === "function" &&
- callbackFn();
- };
- for (var i = 0; i < fileNames.length; i++) {
- this.loadZoneFile(fileNames[i], opts);
- }
- };
- //Get the zone files via XHR -- if the sync flag
- // is set to true, it's being called by the lazy-loading
- // mechanism, so the result needs to be returned inline.
- this.loadZoneFile = function (fileName, opts) {
- if (typeof this.zoneFileBasePath === "undefined") {
- throw new Error(
- "Please define a base path to your zone file directory -- timezoneJS.timezone.zoneFileBasePath."
- );
- }
- //Ignore already loaded zones.
- if (this.loadedZones[fileName]) {
- return;
- }
- this.loadedZones[fileName] = true;
- return builtInLoadZoneFile(fileName, opts);
- };
- this.loadZoneJSONData = function (url, sync) {
- var processData = function (data) {
- data = JSON.parse(data);
- for (var z in data.zones) {
- _this.zones[z] = data.zones[z];
- }
- for (var r in data.rules) {
- _this.rules[r] = data.rules[r];
- }
- };
- return sync
- ? processData(_this.transport({ url: url, async: false }))
- : _this.transport({ url: url, success: processData });
- };
- this.loadZoneDataFromObject = function (data) {
- if (!data) {
- return;
- }
- for (var z in data.zones) {
- _this.zones[z] = data.zones[z];
- }
- for (var r in data.rules) {
- _this.rules[r] = data.rules[r];
- }
- };
- this.getAllZones = function () {
- var arr = [];
- for (var z in this.zones) {
- arr.push(z);
- }
- return arr.sort();
- };
- this.parseZones = function (str) {
- if (!str) {
- return false;
- }
-
- var lines = str.split("\n"),
- arr = [],
- chunk = "",
- l,
- zone = null,
- rule = null;
- for (var i = 0; i < lines.length; i++) {
- l = lines[i];
- if (l.match(/^\s/)) {
- l = "Zone " + zone + l;
- }
- l = l.split("#")[0];
- if (l.length > 3) {
- arr = l.split(/\s+/);
- chunk = arr.shift();
- //Ignore Leap.
- switch (chunk) {
- case "Zone":
- zone = arr.shift();
- if (!_this.zones[zone]) {
- _this.zones[zone] = [];
- }
- if (arr.length < 3) break;
- //Process zone right here and replace 3rd element with the processed array.
- arr.splice(3, arr.length, processZone(arr));
- if (arr[3]) arr[3] = Date.UTC.apply(null, arr[3]);
- arr[0] = -getBasicOffset(arr[0]);
- _this.zones[zone].push(arr);
- break;
- case "Rule":
- rule = arr.shift();
- if (!_this.rules[rule]) {
- _this.rules[rule] = [];
- }
- //Parse int FROM year and TO year
- arr[0] = parseInt(arr[0], 10);
- arr[1] = parseInt(arr[1], 10) || arr[1];
- //Parse time string AT
- arr[5] = parseTimeString(arr[5]);
- //Parse offset SAVE
- arr[6] = getBasicOffset(arr[6]);
- _this.rules[rule].push(arr);
- break;
- case "Link":
- //No zones for these should already exist.
- if (_this.zones[arr[1]]) {
- throw new Error(
- "Error with Link " +
- arr[1] +
- ". Cannot create link of a preexisted zone."
- );
- }
- //Create the link.
- //Links are saved as strings that are the keys
- //of their referenced values.
- //Ex: "US/Central": "America/Chicago"
- if (isNaN(arr[0])) {
- _this.zones[arr[1]] = arr[0];
- } else {
- _this.zones[arr[1]] = parseInt(arr[0], 10);
- }
- break;
- }
- }
- }
- return true;
- };
- //Expose transport mechanism and allow overwrite.
- this.transport = _transport;
- this.getTzInfo = function (dt, tz, isUTC) {
- this.lazyLoadZoneFiles(tz);
- var z = getZone(dt, tz);
- var off = +z[0];
- //See if the offset needs adjustment.
- var rule = getRule(dt, z, isUTC, tz);
- if (rule) {
- off = getAdjustedOffset(off, rule[6]);
- }
- var abbr = getAbbreviation(z, rule);
- return { tzOffset: off, tzAbbr: abbr };
- };
- //Lazy-load any zones not yet loaded.
- this.lazyLoadZoneFiles = function (tz) {
- if (this.loadingScheme === this.loadingSchemes.LAZY_LOAD) {
- //Get the correct region for the zone.
- var zoneFile = getRegionForTimezone(tz);
- if (!zoneFile) {
- throw new Error("Not a valid timezone ID.");
- }
- //Get the file and parse it -- use synchronous XHR.
- this.loadZoneFiles(zoneFile);
- }
- };
- })();
-}.call(typeof window !== "undefined" ? window : this));
-
-// Load all the necessary timezones and their rules
-timezoneJS.timezone.loadingScheme =
- timezoneJS.timezone.loadingSchemes.MANUAL_LOAD;
-timezoneJS.timezone.loadZoneDataFromObject({
- zones: {
- "Atlantic/Cape_Verde": [[60, "-", "-01", null]],
- "Africa/Cairo": [[-120, "Egypt", "EE%sT", null]],
- "Africa/Nairobi": [[-180, "-", "EAT", null]],
- "Africa/Casablanca": [
- [0, "Morocco", "+00/+01", 1540695600000],
- [-60, "Morocco", "+01/+00", null]
- ],
- "Africa/Windhoek": [[-120, "Namibia", "%s", null]],
- "Africa/Johannesburg": [[-120, "SA", "SAST", null]],
- "Africa/Tunis": [[-60, "Tunisia", "CE%sT", null]],
- "Antarctica/Troll": [[0, "Troll", "%s", null]],
- "Asia/Kabul": [[-270, "-", "+0430", null]],
- "Asia/Baku": [[-240, "Azer", "+04/+05", null]],
- "Asia/Dhaka": [[-360, "Dhaka", "+06/+07", null]],
- "Asia/Yangon": [[-390, "-", "+0630", null]],
- "Asia/Shanghai": [[-480, "PRC", "C%sT", null]],
- "Asia/Hong_Kong": [[-480, "HK", "HK%sT", null]],
- "Asia/Taipei": [[-480, "Taiwan", "C%sT", null]],
- "Asia/Nicosia": [[-120, "EUAsia", "EE%sT", null]],
- "Asia/Kolkata": [[-330, "-", "IST", null]],
- "Asia/Tehran": [[-210, "Iran", "+0330/+0430", null]],
- "Asia/Jerusalem": [[-120, "Zion", "I%sT", null]],
- "Asia/Tokyo": [[-540, "Japan", "J%sT", null]],
- "Asia/Amman": [[-120, "Jordan", "EE%sT", null]],
- "Asia/Almaty": [[-360, "-", "ALMT", null]],
- "Asia/Seoul": [[-540, "ROK", "K%sT", null]],
- "Asia/Pyongyang": [
- [-510, "-", "KST", 1525476600000],
- [-540, "-", "KST", null]
- ],
- "Asia/Beirut": [[-120, "Lebanon", "EE%sT", null]],
- "Asia/Kuala_Lumpur": [[-480, "-", "+08", null]],
- "Asia/Hovd": [[-420, "Mongol", "+07/+08", null]],
- "Asia/Ulaanbaatar": [[-480, "Mongol", "+08/+09", null]],
- "Asia/Kathmandu": [[-345, "-", "+0545", null]],
- "Asia/Karachi": [[-300, "Pakistan", "PK%sT", null]],
- "Asia/Hebron": [[-120, "Palestine", "EE%sT", null]],
- "Asia/Riyadh": [[-180, "-", "+03", null]],
- "Asia/Damascus": [[-120, "Syria", "EE%sT", null]],
- "Asia/Bangkok": [[-420, "-", "+07", null]],
- "Asia/Dubai": [[-240, "-", "+04", null]],
- "Australia/Darwin": [[-570, "Aus", "AC%sT", null]],
- "Australia/Perth": [[-480, "AW", "AW%sT", null]],
- "Australia/Eucla": [[-525, "AW", "+0845/+0945", null]],
- "Australia/Brisbane": [[-600, "AQ", "AE%sT", null]],
- "Australia/Adelaide": [[-570, "AS", "AC%sT", null]],
- "Australia/Hobart": [[-600, "AT", "AE%sT", null]],
- "Australia/Melbourne": [[-600, "AV", "AE%sT", null]],
- "Australia/Sydney": [[-600, "AN", "AE%sT", null]],
- "Australia/Lord_Howe": [[-630, "LH", "+1030/+11", null]],
- "Pacific/Fiji": [[-720, "Fiji", "+12/+13", null]],
- "Pacific/Guam": [[-600, "-", "ChST", null]],
- "Pacific/Kiritimati": [[-840, "-", "+14", null]],
- "Pacific/Noumea": [[-660, "NC", "+11/+12", null]],
- "Pacific/Auckland": [[-720, "NZ", "NZ%sT", null]],
- "Pacific/Chatham": [[-765, "Chatham", "+1245/+1345", null]],
- "Pacific/Pago_Pago": [[660, "-", "SST", null]],
- "Pacific/Apia": [[-780, "WS", "+13/+14", null]],
- "Pacific/Tongatapu": [[-780, "Tonga", "+13/+14", null]],
- "Etc/UTC": [[0, "-", "UTC", null]],
- UTC: "Etc/UTC",
- "Europe/London": [[0, "EU", "GMT/BST", null]],
- "Europe/Dublin": [[0, "Eire", "IST/GMT", null]],
- WET: [[0, "EU", "WE%sT", null]],
- CET: [[-60, "C-Eur", "CE%sT", null]],
- MET: [[-60, "C-Eur", "ME%sT", null]],
- EET: [[-120, "EU", "EE%sT", null]],
- "Europe/Brussels": [[-60, "EU", "CE%sT", null]],
- "America/Thule": [[240, "Thule", "A%sT", null]],
- "Europe/Helsinki": [[-120, "EU", "EE%sT", null]],
- "Europe/Paris": [[-60, "EU", "CE%sT", null]],
- "Europe/Berlin": [[-60, "EU", "CE%sT", null]],
- "Europe/Amsterdam": [[-60, "EU", "CE%sT", null]],
- "Atlantic/Azores": [[60, "EU", "-01/+00", null]],
- "Europe/Bucharest": [[-120, "EU", "EE%sT", null]],
- "Europe/Kaliningrad": [[-120, "-", "EET", null]],
- "Europe/Moscow": [[-180, "-", "MSK", null]],
- "Europe/Volgograd": [
- [-180, "-", "+03", 1540692000000],
- [-240, "-", "+04", 1609034400000],
- [-180, "-", "+03", null]
- ],
- "Europe/Samara": [[-240, "-", "+04", null]],
- "Asia/Yekaterinburg": [[-300, "-", "+05", null]],
- "Asia/Omsk": [[-360, "-", "+06", null]],
- "Asia/Novosibirsk": [[-420, "-", "+07", null]],
- "Asia/Novokuznetsk": [[-420, "-", "+07", null]],
- "Asia/Krasnoyarsk": [[-420, "-", "+07", null]],
- "Asia/Irkutsk": [[-480, "-", "+08", null]],
- "Asia/Yakutsk": [[-540, "-", "+09", null]],
- "Asia/Vladivostok": [[-600, "-", "+10", null]],
- "Asia/Magadan": [[-660, "-", "+11", null]],
- "Asia/Srednekolymsk": [[-660, "-", "+11", null]],
- "Asia/Kamchatka": [[-720, "-", "+12", null]],
- "Europe/Belgrade": [[-60, "EU", "CE%sT", null]],
- "Europe/Sarajevo": "Europe/Belgrade",
- "Europe/Istanbul": [[-180, "-", "+03", null]],
- "America/New_York": [[300, "US", "E%sT", null]],
- "America/Chicago": [[360, "US", "C%sT", null]],
- "America/Denver": [[420, "US", "M%sT", null]],
- "America/Los_Angeles": [[480, "US", "P%sT", null]],
- "America/Juneau": [[540, "US", "AK%sT", null]],
- "Pacific/Honolulu": [[600, "-", "HST", null]],
- "America/Phoenix": [[420, "-", "MST", null]],
- "America/St_Johns": [[210, "Canada", "N%sT", null]],
- "America/Halifax": [[240, "Canada", "A%sT", null]],
- "America/Regina": [[360, "-", "CST", null]],
- "America/Mexico_City": [[360, "Mexico", "C%sT", null]],
- "America/Chihuahua": [[420, "Mexico", "M%sT", null]],
- "America/Costa_Rica": [[360, "CR", "C%sT", null]],
- "America/Havana": [[300, "Cuba", "C%sT", null]],
- "America/Port-au-Prince": [[300, "Haiti", "E%sT", null]],
- "America/Panama": [[300, "-", "EST", null]],
- "America/Puerto_Rico": [[240, "-", "AST", null]],
- "America/Argentina/Buenos_Aires": [[180, "Arg", "-03/-02", null]],
- "America/Sao_Paulo": [[180, "Brazil", "-03/-02", null]],
- "America/Santiago": [[240, "Chile", "-04/-03", null]],
- "America/Punta_Arenas": [
- [240, "Chile", "-04/-03", 1480809600000],
- [180, "-", "-03", null]
- ],
- "America/Bogota": [[300, "CO", "-05/-04", null]],
- "America/Asuncion": [[240, "Para", "-04/-03", null]],
- "Atlantic/South_Georgia": [[120, "-", "-02", null]],
- "America/Montevideo": [[180, "Uruguay", "-03/-02", null]],
- "America/Caracas": [[240, "-", "-04", null]],
- // backwards compatibility
- "Europe/Athens": "Europe/Bucharest",
- "Europe/Simferopol": "Europe/Moscow",
- "Asia/Rangoon": "Asia/Yangon",
- "Atlantic/Reykjavik": "UTC",
- "Asia/Kuwait": "Asia/Riyadh",
- "Asia/Muscat": "Asia/Riyadh",
- "Asia/Istanbul": "Europe/Istanbul"
- },
- rules: {
- Egypt: [],
- Morocco: [
- [2013, 2018, "-", "Oct", "lastSun", [3, 0, 0, null], 0, "-"],
- [2014, 2018, "-", "Mar", "lastSun", [2, 0, 0, null], 60, "-"],
- [2017, "only", "-", "May", "21", [3, 0, 0, null], 0, "-"],
- [2017, "only", "-", "Jul", "2", [2, 0, 0, null], 60, "-"],
- [2018, "only", "-", "May", "13", [3, 0, 0, null], 0, "-"],
- [2018, "only", "-", "Jun", "17", [2, 0, 0, null], 60, "-"],
- [2019, "only", "-", "May", "5", [3, 0, 0, null], -60, "-"],
- [2019, "only", "-", "Jun", "9", [2, 0, 0, null], 0, "-"],
- [2020, "only", "-", "Apr", "19", [3, 0, 0, null], -60, "-"],
- [2020, "only", "-", "May", "31", [2, 0, 0, null], 0, "-"],
- [2021, "only", "-", "Apr", "11", [3, 0, 0, null], -60, "-"],
- [2021, "only", "-", "May", "16", [2, 0, 0, null], 0, "-"],
- [2022, "only", "-", "Mar", "27", [3, 0, 0, null], -60, "-"],
- [2022, "only", "-", "May", "8", [2, 0, 0, null], 0, "-"],
- [2023, "only", "-", "Mar", "19", [3, 0, 0, null], -60, "-"],
- [2023, "only", "-", "Apr", "30", [2, 0, 0, null], 0, "-"],
- [2024, "only", "-", "Mar", "10", [3, 0, 0, null], -60, "-"],
- [2024, "only", "-", "Apr", "14", [2, 0, 0, null], 0, "-"],
- [2025, "only", "-", "Feb", "23", [3, 0, 0, null], -60, "-"],
- [2025, "only", "-", "Apr", "6", [2, 0, 0, null], 0, "-"]
- ],
- Namibia: [
- [1994, 2017, "-", "Sep", "Sun>=1", [2, 0, 0, null], 0, "CAT"],
- [1995, 2017, "-", "Apr", "Sun>=1", [2, 0, 0, null], -60, "WAT"]
- ],
- SA: [],
- Tunisia: [],
- Troll: [
- [2005, "max", "-", "Mar", "lastSun", [1, 0, 0, "u"], 120, "+02"],
- [2004, "max", "-", "Oct", "lastSun", [1, 0, 0, "u"], 0, "+00"]
- ],
- EUAsia: [
- [1981, "max", "-", "Mar", "lastSun", [1, 0, 0, "u"], 60, "S"],
- [1996, "max", "-", "Oct", "lastSun", [1, 0, 0, "u"], 0, "-"]
- ],
- Azer: [],
- Dhaka: [],
- PRC: [],
- HK: [],
- Taiwan: [],
- Iran: [
- [2017, 2019, "-", "Mar", "21", [24, 0, 0, null], 60, "-"],
- [2017, 2019, "-", "Sep", "21", [24, 0, 0, null], 0, "-"],
- [2020, "only", "-", "Mar", "20", [24, 0, 0, null], 60, "-"],
- [2020, "only", "-", "Sep", "20", [24, 0, 0, null], 0, "-"],
- [2021, 2023, "-", "Mar", "21", [24, 0, 0, null], 60, "-"],
- [2021, 2023, "-", "Sep", "21", [24, 0, 0, null], 0, "-"],
- [2024, "only", "-", "Mar", "20", [24, 0, 0, null], 60, "-"],
- [2024, "only", "-", "Sep", "20", [24, 0, 0, null], 0, "-"],
- [2025, 2027, "-", "Mar", "21", [24, 0, 0, null], 60, "-"],
- [2025, 2027, "-", "Sep", "21", [24, 0, 0, null], 0, "-"]
- ],
- Zion: [
- [2013, "max", "-", "Mar", "Fri>=23", [2, 0, 0, null], 60, "D"],
- [2013, "max", "-", "Oct", "lastSun", [2, 0, 0, null], 0, "S"]
- ],
- Japan: [],
- Jordan: [
- [2014, "max", "-", "Mar", "lastThu", [24, 0, 0, null], 60, "S"],
- [2014, "max", "-", "Oct", "lastFri", [0, 0, 0, "s"], 0, "-"]
- ],
- ROK: [],
- Lebanon: [
- [1993, "max", "-", "Mar", "lastSun", [0, 0, 0, null], 60, "S"],
- [1999, "max", "-", "Oct", "lastSun", [0, 0, 0, null], 0, "-"]
- ],
- Mongol: [],
- Pakistan: [],
- Palestine: [
- [2016, 2018, "-", "Mar", "Sat>=24", [1, 0, 0, null], 60, "S"],
- [2016, 2018, "-", "Oct", "Sat>=24", [1, 0, 0, null], 0, "-"],
- [2019, "only", "-", "Mar", "29", [0, 0, 0, null], 60, "S"],
- [2019, "only", "-", "Oct", "Sat>=24", [0, 0, 0, null], 60, "-"],
- [2020, "max", "-", "Mar", "Sat>=24", [0, 0, 0, null], 60, "S"],
- [2020, "max", "-", "Oct", "Sat>=24", [1, 0, 0, null], 60, "-"]
- ],
- Syria: [
- [2012, "max", "-", "Mar", "lastFri", [0, 0, 0, null], 60, "S"],
- [2009, "max", "-", "Oct", "lastFri", [0, 0, 0, null], 0, "-"]
- ],
- Aus: [],
- AW: [],
- AQ: [],
- AS: [
- [2008, "max", "-", "Apr", "Sun>=1", [2, 0, 0, "s"], 0, "S"],
- [2008, "max", "-", "Oct", "Sun>=1", [2, 0, 0, "s"], 60, "D"]
- ],
- AT: [
- [2001, "max", "-", "Oct", "Sun>=1", [2, 0, 0, "s"], 60, "D"],
- [2008, "max", "-", "Apr", "Sun>=1", [2, 0, 0, "s"], 0, "S"]
- ],
- AV: [
- [2008, "max", "-", "Apr", "Sun>=1", [2, 0, 0, "s"], 0, "S"],
- [2008, "max", "-", "Oct", "Sun>=1", [2, 0, 0, "s"], 60, "D"]
- ],
- AN: [
- [2008, "max", "-", "Apr", "Sun>=1", [2, 0, 0, "s"], 0, "S"],
- [2008, "max", "-", "Oct", "Sun>=1", [2, 0, 0, "s"], 60, "D"]
- ],
- LH: [
- [2008, "max", "-", "Apr", "Sun>=1", [2, 0, 0, null], 0, "-"],
- [2008, "max", "-", "Oct", "Sun>=1", [2, 0, 0, null], 30, "-"]
- ],
- Fiji: [
- [2014, 2018, "-", "Nov", "Sun>=1", [2, 0, 0, null], 60, "-"],
- [2015, "max", "-", "Jan", "Sun>=12", [3, 0, 0, null], 0, "-"],
- [2019, "only", "-", "Nov", "Sun>=8", [2, 0, 0, null], 60, "-"],
- [2020, "only", "-", "Dec", "20", [2, 0, 0, null], 60, "-"],
- [2021, "max", "-", "Nov", "Sun>=8", [2, 0, 0, null], 60, "-"]
- ],
- NC: [],
- NZ: [
- [2007, "max", "-", "Sep", "lastSun", [2, 0, 0, "s"], 60, "D"],
- [2008, "max", "-", "Apr", "Sun>=1", [2, 0, 0, "s"], 0, "S"]
- ],
- Chatham: [
- [2007, "max", "-", "Sep", "lastSun", [2, 45, 0, "s"], 60, "-"],
- [2008, "max", "-", "Apr", "Sun>=1", [2, 45, 0, "s"], 0, "-"]
- ],
- WS: [
- [2012, "max", "-", "Apr", "Sun>=1", [4, 0, 0, null], 0, "-"],
- [2012, "max", "-", "Sep", "lastSun", [3, 0, 0, null], 60, "-"]
- ],
- Tonga: [
- [2016, "only", "-", "Nov", "Sun>=1", [2, 0, 0, null], 60, "-"],
- [2017, "only", "-", "Jan", "Sun>=15", [3, 0, 0, null], 0, "-"]
- ],
- Eire: [
- [1981, "max", "-", "Mar", "lastSun", [1, 0, 0, "u"], 0, "-"],
- [1996, "max", "-", "Oct", "lastSun", [1, 0, 0, "u"], -60, "-"]
- ],
- EU: [
- [1981, "max", "-", "Mar", "lastSun", [1, 0, 0, "u"], 60, "S"],
- [1996, "max", "-", "Oct", "lastSun", [1, 0, 0, "u"], 0, "-"]
- ],
- "C-Eur": [
- [1981, "max", "-", "Mar", "lastSun", [2, 0, 0, "s"], 60, "S"],
- [1996, "max", "-", "Oct", "lastSun", [2, 0, 0, "s"], 0, "-"]
- ],
- Thule: [
- [2007, "max", "-", "Mar", "Sun>=8", [2, 0, 0, null], 60, "D"],
- [2007, "max", "-", "Nov", "Sun>=1", [2, 0, 0, null], 0, "S"]
- ],
- US: [
- [2007, "max", "-", "Mar", "Sun>=8", [2, 0, 0, null], 60, "D"],
- [2007, "max", "-", "Nov", "Sun>=1", [2, 0, 0, null], 0, "S"]
- ],
- Canada: [
- [2007, "max", "-", "Mar", "Sun>=8", [2, 0, 0, null], 60, "D"],
- [2007, "max", "-", "Nov", "Sun>=1", [2, 0, 0, null], 0, "S"]
- ],
- Mexico: [
- [2002, "max", "-", "Apr", "Sun>=1", [2, 0, 0, null], 60, "D"],
- [2002, "max", "-", "Oct", "lastSun", [2, 0, 0, null], 0, "S"]
- ],
- CR: [],
- Cuba: [
- [2012, "max", "-", "Nov", "Sun>=1", [0, 0, 0, "s"], 0, "S"],
- [2013, "max", "-", "Mar", "Sun>=8", [0, 0, 0, "s"], 60, "D"]
- ],
- Haiti: [
- [2017, "max", "-", "Mar", "Sun>=8", [2, 0, 0, null], 60, "D"],
- [2017, "max", "-", "Nov", "Sun>=1", [2, 0, 0, null], 0, "S"]
- ],
- Arg: [],
- Brazil: [
- [2008, 2017, "-", "Oct", "Sun>=15", [0, 0, 0, null], 60, "-"],
- [2016, 2019, "-", "Feb", "Sun>=15", [0, 0, 0, null], 0, "-"],
- [2018, "only", "-", "Nov", "Sun>=1", [0, 0, 0, null], 60, "-"]
- ],
- Chile: [
- [2016, 2018, "-", "May", "Sun>=9", [3, 0, 0, "u"], 0, "-"],
- [2016, 2018, "-", "Aug", "Sun>=9", [4, 0, 0, "u"], 60, "-"],
- [2019, "max", "-", "Apr", "Sun>=2", [3, 0, 0, "u"], 0, "-"],
- [2019, "max", "-", "Sep", "Sun>=2", [4, 0, 0, "u"], 60, "-"]
- ],
- CO: [],
- Para: [
- [2010, "max", "-", "Oct", "Sun>=1", [0, 0, 0, null], 60, "-"],
- [2013, "max", "-", "Mar", "Sun>=22", [0, 0, 0, null], 0, "-"]
- ],
- Uruguay: []
- }
-});
-
-};
-
-
-let __js_standard_visualization_ = (_exports) => {
-
-/* global _CIQ, _timezoneJS, _SplinePlotter */
-
-var CIQ = typeof _CIQ !== "undefined" ? _CIQ : _exports.CIQ;
-
-/**
- * Creates a DOM object capable of receiving a data stream. The object changes as a result of the incoming data.
- * The constructor function takes attributes that define how and where in the HTML document the object gets created.
- * See {@link CIQ.Visualization#setAttributes} for more information on attributes.
- *
- * One useful application of this is to render an SVG graphic.
- *
- * Methods are provided to pass data into the object and to render it in the HTML document. Note that the `data` and
- * `attributes` that are passed into the prototype methods of this object become owned by it and therefore can be mutated.
- *
- * The DOM object-generating function can assign class names to subelements within the object. These class names can be used
- * to style the object using CSS. Documentation for the built-in functions explains which classes are available to be styled.
- *
- * @param {object} attributes Parameters to be used when creating the object.
- * @param {function} attributes.renderFunction DOM object-generating function. Takes data as an array (sorted by index property)
- * and attributes as arguments *by reference* and returns an `HTMLElement` (which may have children).
- * @param {HTMLElement|string} [attributes.container] Element in which to put the DOM object (or selector thereof). If omitted,
- * a container element is created with 300 x 300 pixel dimensions.
- * @param {boolean} [attributes.useCanvasShim] Set to true to relocate the container behind the canvas but in front of the
- * gridlines. **Note:** Consider using {@link CIQ.ChartEngine#embedVisualization}; it automatically places the object
- * within the canvases.
- * @param {CIQ.ChartEngine} [attributes.stx] A reference to the chart engine. Required if using the canvas shim.
- * @param {string} [attributes.id] Optional id attribute to assign to the object.
- * @param {boolean} [attributes.forceReplace] True to force a complete replacement of the DOM object when data changes.
- * Do not set if `renderFunction` can handle an incremental update of the object. Alternatively, `renderFunction` might set
- * this attribute. When attributes are updated using `setAttributes`, a complete replacement occurs.
- * @constructor
- * @name CIQ.Visualization
- * @example
- * let svg=new CIQ.Visualization({ renderFunction: CIQ.SVGChart.renderPieChart });
- * svg.updateData({"Low":{name:"low", value:30}, "High":{name:"high", value:70}});
- * @tsdeclaration
- * constructor(
- * attributes: {
- * renderFunction: Function,
- * container?: HTMLElement|string,
- * useCanvasShim?: boolean,
- * stx?: CIQ.ChartEngine,
- * id?: string,
- * forceReplace?: boolean,
- * [renderAttributes:string]: any
- * }
- * )
- * @since 7.4.0
- */
-CIQ.Visualization =
- CIQ.Visualization ||
- function (attributes) {
- if (!attributes) {
- console.log("CIQ.Visualization() missing attributes argument.");
- return;
- }
- if (typeof attributes.renderFunction !== "function") {
- console.log(
- "CIQ.Visualization() missing renderFunction property in attributes."
- );
- return;
- }
- /**
- * READ ONLY. The DOM container that hosts the DOM object.
- *
- * @type HTMLElement
- * @memberof CIQ.Visualization
- * @since 7.4.0
- */
- this.container = null;
- /**
- * READ ONLY. The attributes used to render the DOM object. See the [function description]{@link CIQ.Visualization}
- * for details. Do not change this property directly; instead, use {@link CIQ.Visualization#setAttributes}.
- * @type object
- * @memberof CIQ.Visualization
- * @since 7.4.0
- */
- this.attributes = attributes;
- /**
- * READ ONLY. The data used to render the DOM object. See the [function description]{@link CIQ.Visualization}
- * for details. Do not change this property directly; instead, use {@link CIQ.Visualization#updateData}.
- * @type object
- * @memberof CIQ.Visualization
- * @since 7.4.0
- */
- this.data = null;
- /**
- * READ ONLY. The DOM object created by the rendering function.
- *
- * @type HTMLElement
- * @memberof CIQ.Visualization
- * @since 7.4.0
- */
- this.object = null;
- };
-CIQ.extend(CIQ.Visualization.prototype, {
- /**
- * Removes the DOM object. If the container was generated by this object, the container is also removed.
- *
- * @param {boolean} soft True to leave properties of this object alone. Setting to false is preferable.
- * @memberof CIQ.Visualization#
- * @since 7.4.0
- */
- destroy: function (soft) {
- var container = this.container;
- CIQ.resizeObserver(container, null, container.resizeHandle);
- if (container.autoGenerated) {
- container.remove();
- delete this.container;
- } else container.innerHTML = "";
- if (soft) return;
-
- // suicide!!!
- this.attributes = null;
- this.container = null;
- this.data = null;
- this.object = null;
- this.destroy = this.draw = this.setAttributes = function () {};
- this.updateData = function () {
- return undefined;
- };
- },
- /**
- * Draws the DOM object in its container. Data must be set using {@link CIQ.Visualization#updateData} prior
- * to calling this function. Any content existing within the container is removed prior to drawing the object.
- *
- * @param {boolean} forceReplace Indicates whether a full redraw is requested.
- * @since 7.4.0
- * @memberof CIQ.Visualization#
- */
- draw: function (forceReplace) {
- if (!this.data || typeof this.data !== "object") {
- console.log("CIQ.Visualization.draw() missing data.");
- return;
- }
-
- function sortFcn(l, r) {
- return l.index < r.index ? -1 : l.index > r.index ? 1 : 0;
- }
-
- var attributes = this.attributes;
- var container = attributes.container || this.container;
- if (typeof container === "string")
- container = document.querySelector(container);
-
- if (!container) {
- container = document.createElement("div");
- container.style.height = container.style.width = "300px";
- document.body.appendChild(container);
- container.autoGenerated = true;
- }
- if (attributes.stx) {
- var shim = attributes.stx.chart.canvasShim;
- if (
- attributes.useCanvasShim &&
- shim &&
- shim !== container &&
- shim !== container.parentNode
- ) {
- if (!container.autoGenerated) {
- container = container.cloneNode();
- container.id = "";
- container.autoGenerated = true;
- }
- shim.appendChild(container);
- }
- }
- if (this.container && this.container !== container) {
- this.destroy(true);
- }
- if (!container.resizeHandle) {
- var closure = function (me) {
- return function () {
- if (me.data && me.container && document.body.contains(me.container)) {
- me.draw.call(me, true);
- }
- };
- };
- container.resizeHandle = CIQ.resizeObserver(
- container,
- closure(this),
- null,
- 100
- );
- }
- this.container = container;
- this.attributes = attributes;
-
- attributes = CIQ.ensureDefaults(
- { container: this.container },
- this.attributes
- );
- var object = attributes.renderFunction(
- Object.values(this.data).sort(sortFcn),
- attributes
- );
- if (object) {
- if (attributes.id) object.id = attributes.id;
- if (forceReplace || attributes.forceReplace) {
- this.container.innerHTML = "";
- this.container.appendChild(object);
- }
- }
- this.attributes = attributes;
- this.object = object;
- },
- /**
- * Adds or changes the visualization object attributes, and then calls the draw function.
- *
- * The following generic attributes are available to all objects; all attributes are passed into the object-generating
- * function and may be used there:
- * - renderFunction
- * - container
- * - stx
- * - useCanvasShim
- * - id
- * - forceReplace
- *
- * Attributes are passed into `renderFunction`, the object-generating function; and so, additional attributes can be
- * added specific to the function.
- *
- * **Note:** The attributes passed into `renderFunction` can be changed by the render function when necessary. You can
- * set either one attribute by passing in a key and a value, or you can add a set of attributes by passing in an object
- * of key/value pairs.
- *
- * @param {object|string} arg1 An attribute key or and object of attribute key/value pairs.
- * @param {*} [arg2] The value of the attribute if passing in one key and value.
- * @memberof CIQ.Visualization#
- * @since 7.4.0
- */
- setAttributes: function (arg1, arg2) {
- var forceAttrs = [
- "renderFunction",
- "container",
- "stx",
- "useCanvasShim",
- "id",
- "forceReplace"
- ];
- var useForce = false;
- var attr = arg1;
- if (typeof arg1 == "string") {
- attr = {};
- attr[arg1] = arg2;
- }
- if (typeof attr == "object") {
- for (var key in attr) {
- if (
- this.attributes[key] !== attr[key] &&
- forceAttrs.indexOf(key) !== -1
- )
- useForce = true;
- this.attributes[key] = attr[key];
- }
- }
- this.draw(useForce);
- },
- /**
- * Adds or changes the visualization object data, and then calls the draw function.
- *
- * @param {(object|array)} data Provides data used to generate the DOM object. Contains at a minimum a `name`, a `value`,
- * and an optional `index`, which specifies sort order. The data must accommodate the update `action`.
- * @param {string} [action] The action to take when generating the DOM object. Valid actions are "add", "update",
- * "delete", and "replace" (default).
- *
- * The `data` object provides each action with the required data.
- *
- * | Action | Required Data |
- * | ------ | ---- |
- * | replace | A full data object. |
- * | delete | The data records to remove. **Note:** This may affect the colors used in the chart.
- * | update | The data records to update. The existing records will have their properties replaced with the new properties, leaving all non-matching properties alone.
- * | add | The same as the "update" action except the `value` property of the existing data is augmented instead of replaced by the new value.
- *
- * See the examples below.
- *
- * **Note:** If only the `value` property is being changed, it may be passed as a raw number rather than being assigned
- * to an object property.
- *
- * @example
- * obj
:>10),s%1024+56320)),(n+1===t||16384