diff --git a/src/components/multiRangeSlider/MultiRangeSlider.js b/src/components/multiRangeSlider/MultiRangeSlider.js
new file mode 100644
index 000000000..5e0a9a478
--- /dev/null
+++ b/src/components/multiRangeSlider/MultiRangeSlider.js
@@ -0,0 +1,93 @@
+import React, { useCallback, useEffect, useRef, useState } from 'react';
+import PropTypes from 'prop-types';
+import './multiRangeSlider.css';
+
+const MultiRangeSlider = ({ min, max, onChange }) => {
+ const [minVal, setMinVal] = useState(min);
+ const [maxVal, setMaxVal] = useState(max);
+ const minValRef = useRef(min);
+ const maxValRef = useRef(max);
+ const range = useRef(null);
+
+ // Convert to percentage
+ const getPercent = useCallback(
+ (value) => Math.round(((value - min) / (max - min)) * 100),
+ [min, max],
+ );
+
+ // Set width of the range to decrease from the left side
+ useEffect(() => {
+ const minPercent = getPercent(minVal);
+ const maxPercent = getPercent(maxValRef.current);
+
+ if (range.current) {
+ range.current.style.left = `${minPercent}%`;
+ range.current.style.width = `${maxPercent - minPercent}%`;
+ }
+ }, [minVal, getPercent]);
+
+ // Set width of the range to decrease from the right side
+ useEffect(() => {
+ const minPercent = getPercent(minValRef.current);
+ const maxPercent = getPercent(maxVal);
+
+ if (range.current) {
+ range.current.style.width = `${maxPercent - minPercent}%`;
+ }
+ }, [maxVal, getPercent]);
+
+ // Get min and max values when their state changes
+ useEffect(() => {
+ onChange({ min: minVal, max: maxVal });
+ }, [minVal, maxVal, onChange]);
+
+ return (
+ <>
+
+ {'Y axis range'}
+
+
+
{
+ const value = Math.min(Number(event.target.value), maxVal - 1);
+
+ setMinVal(value);
+ minValRef.current = value;
+ }}
+ className='mrs_thumb mrs_thumb--left'
+ style={{ zIndex: minVal > max - 100 && '5' }} />
+
{
+ const value = Math.max(Number(event.target.value), minVal + 1);
+
+ setMaxVal(value);
+ maxValRef.current = value;
+ }}
+ className='mrs_thumb mrs_thumb--right' />
+
+
+
+
+
{minVal}
+
{maxVal}
+
+
+ >
+ );
+};
+
+MultiRangeSlider.propTypes = {
+ min: PropTypes.number.isRequired,
+ max: PropTypes.number.isRequired,
+ onChange: PropTypes.func.isRequired,
+};
+
+export default MultiRangeSlider;
diff --git a/src/components/multiRangeSlider/multiRangeSlider.css b/src/components/multiRangeSlider/multiRangeSlider.css
new file mode 100644
index 000000000..abf219bcb
--- /dev/null
+++ b/src/components/multiRangeSlider/multiRangeSlider.css
@@ -0,0 +1,108 @@
+.mrs_container {
+ all: revert;
+ height: 3vh;
+ display: flex;
+ align-items: left;
+ justify-content: center;
+}
+
+.mrs_slider {
+ all: revert;
+ position: relative;
+ width: 200px;
+}
+
+.mrs_slider__track,
+.mrs_slider__range,
+.mrs_slider__left-value,
+.mrs_slider__right-value {
+ all: revert;
+ position: absolute;
+}
+
+.mrs_slider__track,
+.mrs_slider__range {
+ border-radius: 3px;
+ height: 5px;
+}
+
+.mrs_slider__track {
+ background-color: #949494;
+ width: 100%;
+ z-index: 1;
+}
+
+.mrs_slider__range {
+ background-color: #5947ff;
+ z-index: 2;
+}
+
+.mrs_slider__left-value,
+.mrs_slider__right-value {
+ color: #000000;
+ font-size: 12px;
+ margin-top: 20px;
+}
+
+.mrs_slider__left-value {
+ left: 6px;
+}
+
+.mrs_slider__right-value {
+ right: -4px;
+}
+
+/* Removing the default appearance */
+.mrs_thumb,
+.mrs_thumb::-webkit-slider-thumb {
+ all: revert;
+ -webkit-appearance: none;
+ -webkit-tap-highlight-color: transparent;
+}
+
+.mrs_thumb {
+ all: revert;
+ pointer-events: none;
+ position: absolute;
+ height: 0;
+ width: 200px;
+ outline: none;
+}
+
+.mrs_thumb--left {
+ z-index: 3;
+}
+
+.mrs_thumb--right {
+ z-index: 4;
+}
+
+/* For Chrome browsers */
+.mrs_thumb::-webkit-slider-thumb {
+ all: revert;
+ background-color: #f1f5f7;
+ border: none;
+ border-radius: 50%;
+ box-shadow: 0 0 1px 1px #ced4da;
+ cursor: pointer;
+ height: 18px;
+ width: 18px;
+ margin-top: 4px;
+ pointer-events: all;
+ position: relative;
+}
+
+/* For Firefox browsers */
+.mrs_thumb::-moz-range-thumb {
+ all: revert;
+ background-color: #f1f5f7;
+ border: none;
+ border-radius: 50%;
+ box-shadow: 0 0 1px 1px #ced4da;
+ cursor: pointer;
+ height: 18px;
+ width: 18px;
+ margin-top: 4px;
+ pointer-events: all;
+ position: relative;
+}
\ No newline at end of file
diff --git a/src/env/development.js b/src/env/development.js
index fb495d2a6..7799783ba 100644
--- a/src/env/development.js
+++ b/src/env/development.js
@@ -71,7 +71,7 @@ import { TimeSeriesSingle } from '../examples/timeseries_single';
*/
// import { AllTypes } from "../examples/all_types";
-const data = VideoRectangles;
+const data = TimeSeries;
function getData(task) {
if (task && task.data) {
diff --git a/src/tags/object/TimeSeries.js b/src/tags/object/TimeSeries.js
index 6b7671274..15f1e34e4 100644
--- a/src/tags/object/TimeSeries.js
+++ b/src/tags/object/TimeSeries.js
@@ -27,6 +27,8 @@ import PersistentStateMixin from '../../mixins/PersistentState';
import './TimeSeries/Channel';
import { AnnotationMixin } from '../../mixins/AnnotationMixin';
+import MultiRangeSlider from '../../components/multiRangeSlider/MultiRangeSlider';
+
/**
* The `TimeSeries` tag can be used to label time series data. Read more about Time Series Labeling on [the time series template page](../templates/time_series.html).
*
@@ -66,6 +68,7 @@ import { AnnotationMixin } from '../../mixins/AnnotationMixin';
* @param {string} [durationDisplayFormat] Format used to display temporal duration value for brush range. If the temporal column is a date, use strftime to format it. If it's a number, use [d3 number](https://github.com/d3/d3-format#locale_format) formatting.
* @param {string} [sep=,] Separator for your CSV file.
* @param {string} [overviewChannels] Comma-separated list of channel names or indexes displayed in overview.
+ * @param {string} [ylim] Comma-separated list of y axis limits.
* @param {string} [overviewWidth=25%] Default width of overview window in percents
* @param {boolean} [fixedScale=false] Whether to scale y-axis to the maximum to fit all the values. If false, current view scales to fit only the displayed values.
*/
@@ -80,7 +83,7 @@ const TagAttrs = types.model({
durationdisplayformat: '.0f',
overviewchannels: '', // comma-separated list of channels to show
overviewwidth: '25%',
-
+ ylim: '', // comma-separated list of y axis limits
fixedscale: false,
multiaxis: types.optional(types.boolean, false), // show channels in the same view
@@ -819,6 +822,7 @@ const Overview = observer(({ item, data, series }) => {
return ;
});
+
const HtxTimeSeriesViewRTS = ({ item }) => {
const ref = React.createRef();
@@ -836,12 +840,26 @@ const HtxTimeSeriesViewRTS = ({ item }) => {
);
+ const ylim = item.ylim;
+ const datarange = ylim.split(',');
+
return (
-
+
{Tree.renderChildren(item, item.annotation)}
+ {item.ylim && (
+
+ {
+ item.updatedYLim = [min, max];
+ }}
+ />
+
+ )}
);
};
diff --git a/src/tags/object/TimeSeries/Channel.js b/src/tags/object/TimeSeries/Channel.js
index 60704dd94..80162223e 100644
--- a/src/tags/object/TimeSeries/Channel.js
+++ b/src/tags/object/TimeSeries/Channel.js
@@ -758,6 +758,29 @@ class ChannelD3 extends React.Component {
translateY = min / diffY;
this.y.domain([min, max]);
+
+ } else {
+ const ylim = item.parent?.ylim;
+
+ if (ylim !== '') {
+ // use custom range
+ const updatedylim = item.parent?.updatedYLim;
+
+ if (typeof updatedylim === 'undefined') {
+ const datarange = ylim.split(',');
+
+ let min = 0;
+ let max = 1;
+
+ if (datarange[0] !== '') min = Number(datarange[0]);
+ if (datarange[1] !== '') max = Number(datarange[1]);
+
+ this.y.domain([min, max]);
+
+ } else {
+ this.y.domain([updatedylim[0], updatedylim[1]]);
+ }
+ }
}
// zoomStep - zoom level when we need to switch between optimized and original data