Skip to content
This repository was archived by the owner on Apr 18, 2024. It is now read-only.

feat: Custom y-axis in TimeSeries #1206

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 93 additions & 0 deletions src/components/multiRangeSlider/MultiRangeSlider.js
Original file line number Diff line number Diff line change
@@ -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 (
<>
<div className='mrs_container'>
{'Y axis range'}
</div>
<div className='mrs_container'>
<input
type='range'
min={min}
max={max}
value={minVal}
onChange={(event) => {
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' }} />
<input
type='range'
min={min}
max={max}
value={maxVal}
onChange={(event) => {
const value = Math.max(Number(event.target.value), minVal + 1);

setMaxVal(value);
maxValRef.current = value;
}}
className='mrs_thumb mrs_thumb--right' />

<div className='mrs_slider'>
<div className='mrs_slider__track' />
<div ref={range} className='mrs_slider__range' />
<div className='mrs_slider__left-value'>{minVal}</div>
<div className='mrs_slider__right-value'>{maxVal}</div>
</div>
</div>
</>
);
};

MultiRangeSlider.propTypes = {
min: PropTypes.number.isRequired,
max: PropTypes.number.isRequired,
onChange: PropTypes.func.isRequired,
};

export default MultiRangeSlider;
108 changes: 108 additions & 0 deletions src/components/multiRangeSlider/multiRangeSlider.css
Original file line number Diff line number Diff line change
@@ -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;
}
2 changes: 1 addition & 1 deletion src/env/development.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
22 changes: 20 additions & 2 deletions src/tags/object/TimeSeries.js
Original file line number Diff line number Diff line change
Expand Up @@ -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).
*
Expand Down Expand Up @@ -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.
*/
Expand All @@ -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
Expand Down Expand Up @@ -819,6 +822,7 @@ const Overview = observer(({ item, data, series }) => {
return <div className="htx-timeseries-overview" ref={ref} />;
});


const HtxTimeSeriesViewRTS = ({ item }) => {
const ref = React.createRef();

Expand All @@ -836,12 +840,26 @@ const HtxTimeSeriesViewRTS = ({ item }) => {
</div>
);

const ylim = item.ylim;
const datarange = ylim.split(',');

return (
<div ref={ref} className="htx-timeseries">
<div className="htx-timeseries" ref={ref}>
<ObjectTag item={item}>
{Tree.renderChildren(item, item.annotation)}
<Overview data={item.dataObj} series={item.dataHash} item={item} range={item.brushRange} />
</ObjectTag>
{item.ylim && (
<div className="ylim-slider">
<MultiRangeSlider
min={Number(datarange[0])}
max={Number(datarange[1])}
onChange={({ min, max }) => {
item.updatedYLim = [min, max];
}}
/>
</div>
)}
</div>
);
};
Expand Down
23 changes: 23 additions & 0 deletions src/tags/object/TimeSeries/Channel.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down