Reactifyf scale Y

This commit is contained in:
Franc Camps-Febrer
2019-01-03 18:41:57 +01:00
parent d8b27cea54
commit a0c654aafc
4 changed files with 92 additions and 69 deletions

View File

@@ -13,6 +13,7 @@ import TimelineLogic from '../js/timeline/timeline.js';
import TimelineLabels from './TimelineLabels.jsx'; import TimelineLabels from './TimelineLabels.jsx';
import TimelineMarkers from './TimelineMarkers.jsx' import TimelineMarkers from './TimelineMarkers.jsx'
import TimelineEvents from './TimelineEvents.jsx'; import TimelineEvents from './TimelineEvents.jsx';
import TimelineCategories from './TimelineCategories.jsx';
class Timeline extends React.Component { class Timeline extends React.Component {
constructor(props) { constructor(props) {
@@ -27,12 +28,15 @@ class Timeline extends React.Component {
height_controls: 115, height_controls: 115,
margin_left: 120 margin_left: 120
}, },
softTimeUpdate: 0 softTimeUpdate: 0,
scaleY: null
}; };
} }
componentDidMount() { componentDidMount() {
this.methods = Object.assign({}, this.props.methods, { onSoftUpdate: (toggle) => { this.setState({ softTimeUpdate: toggle }) }}); this.methods = Object.assign({}, this.props.methods, {
onSoftUpdate: (toggle) => { this.setState({ softTimeUpdate: toggle }) }
});
this.timeline = new TimelineLogic(this.svgRef.current, this.props.ui, this.methods); this.timeline = new TimelineLogic(this.svgRef.current, this.props.ui, this.methods);
this.timeline.update(this.props.domain.categories, this.props.app.timerange); this.timeline.update(this.props.domain.categories, this.props.app.timerange);
this.computeDims(); this.computeDims();
@@ -42,9 +46,23 @@ class Timeline extends React.Component {
componentWillReceiveProps(nextProps) { componentWillReceiveProps(nextProps) {
if (hash(nextProps) !== hash(this.props)) { if (hash(nextProps) !== hash(this.props)) {
this.timeline.update(nextProps.domain.categories, nextProps.app.timerange); this.timeline.update(nextProps.domain.categories, nextProps.app.timerange);
let groupYs = Array.apply(null, Array(nextProps.domain.categories.length));
groupYs = groupYs.map((g, i) => (i + 1) * 80 / groupYs.length);
this.setState({ scaleY: d3.scaleOrdinal().domain(nextProps.domain.categories).range(groupYs) });
} }
} }
/**
* Get y height of eventPoint, considering the ordinal Y scale
* @param {object} eventPoint: regular eventPoint data
*/
getEventY(eventPoint) {
return this.state.scaleY(eventPoint.category);
}
onClickArrow() { onClickArrow() {
this.setState((prevState, props) => { this.setState((prevState, props) => {
return {isFolded: !prevState.isFolded}; return {isFolded: !prevState.isFolded};
@@ -84,6 +102,10 @@ class Timeline extends React.Component {
height={dims.height} height={dims.height}
> >
<TimelineClip dims={dims} /> <TimelineClip dims={dims} />
<TimelineCategories
timeline={this.timeline}
categories={this.props.domain.categories}
/>
<TimelineHandles <TimelineHandles
dims={dims} dims={dims}
onMoveTime={(dir) => { this.onMoveTime(dir) }} onMoveTime={(dir) => { this.onMoveTime(dir) }}
@@ -100,12 +122,12 @@ class Timeline extends React.Component {
<TimelineMarkers <TimelineMarkers
selected={this.props.app.selected} selected={this.props.app.selected}
getEventX={(e) => this.timeline.getEventX(e)} getEventX={(e) => this.timeline.getEventX(e)}
getEventY={(e) => this.timeline.getEventY(e)} getEventY={(e) => this.getEventY(e)/*this.timeline.getEventY(e)*/}
/> />
<TimelineEvents <TimelineEvents
events={this.props.domain.events} events={this.props.domain.events}
getEventX={(e) => this.timeline.getEventX(e)} getEventX={(e) => this.timeline.getEventX(e)}
getEventY={(e) => this.timeline.getEventY(e)} getEventY={(e) => this.getEventY(e)/*this.timeline.getEventY(e)*/}
getCategoryColor={this.props.methods.getCategoryColor} getCategoryColor={this.props.methods.getCategoryColor}
onSelect={this.props.methods.onSelect} onSelect={this.props.methods.onSelect}
/> />

View File

@@ -0,0 +1,55 @@
import React from 'react';
class TimelineCategories extends React.Component {
constructor() {
super();
this.grabRef = React.createRef()
this.state = {
isInitialized: false
}
}
componentDidUpdate() {
if (!this.state.isInitialized && this.props.timeline) {
const drag = d3.drag()
.on('start', this.props.timeline.onDragStart)
.on('drag', this.props.timeline.onDrag)
.on('end', this.props.timeline.onDragEnd);
d3.select(this.grabRef.current)
.call(drag);
this.setState({ isInitialized: true });
}
}
getY(idx) {
return (idx + 1) * 80 / this.props.categories.length
}
renderCategory(category, idx) {
return (
<g class="tick" opacity="1" transform={`translate(0,${this.getY(idx)})`}>
<line stroke="currentColor" x1={120} x2={1026}></line>
<text fill="currentColor" x={120} dy="0.32em">{category.category}</text>
</g>
)
}
render () {
return (
<g
transform="translate(0, 0)" class="yAxis" fill="none"
>
{this.props.categories.map((cat, idx) => this.renderCategory(cat, idx))}
<rect
ref={this.grabRef}
class="drag-grabber" x="120" y="20" width="906" height="80"
></rect>
</g>
);
}
}
export default TimelineCategories;

View File

@@ -2,7 +2,6 @@
TIMELINE TIMELINE
Displays events over the course of the night Displays events over the course of the night
Allows brushing and selecting periods of time in it Allows brushing and selecting periods of time in it
TODO: is it possible to express this idiomatically as React?
*/ */
import { parseDate } from '../utilities'; import { parseDate } from '../utilities';
import hash from 'object-hash'; import hash from 'object-hash';
@@ -35,9 +34,6 @@ export default function(svg, ui, methods) {
.domain(timerange) .domain(timerange)
.range([margin.left, WIDTH]); .range([margin.left, WIDTH]);
scale.y = d3.scaleOrdinal()
/** /**
* Initilize SVG elements and groups * Initilize SVG elements and groups
*/ */
@@ -79,10 +75,6 @@ export default function(svg, ui, methods) {
.tickSize(0) .tickSize(0)
.tickFormat(d3.timeFormat('%H:%M')); .tickFormat(d3.timeFormat('%H:%M'));
axis.y =
d3.axisLeft(scale.y)
.tickValues([]);
updateAxis(); updateAxis();
/** /**
@@ -103,7 +95,6 @@ export default function(svg, ui, methods) {
WIDTH = getCurrentWidth() - WIDTH_CONTROLS; WIDTH = getCurrentWidth() - WIDTH_CONTROLS;
scale.x.range([margin.left, WIDTH]); scale.x.range([margin.left, WIDTH]);
axis.y.tickSize(WIDTH - margin.left);
dom.axis.y.attr('transform', `translate(${WIDTH}, 0)`) dom.axis.y.attr('transform', `translate(${WIDTH}, 0)`)
render(null); render(null);
} }
@@ -111,14 +102,6 @@ export default function(svg, ui, methods) {
} }
addResizeListener(); addResizeListener();
/**
* Get y height of eventPoint, considering the ordinal Y scale
* @param {object} eventPoint: regular eventPoint data
*/
function getEventY(eventPoint) {
const yGroup = eventPoint.category;
return scale.y(yGroup);
}
/** /**
* Get x position of eventPoint, considering the time scale * Get x position of eventPoint, considering the time scale
@@ -184,6 +167,7 @@ export default function(svg, ui, methods) {
* Setup drag behavior * Setup drag behavior
*/ */
function onDragStart(ev) { function onDragStart(ev) {
console.log('ohoh')
d3.event.sourceEvent.stopPropagation(); d3.event.sourceEvent.stopPropagation();
dragPos0 = d3.event.x; dragPos0 = d3.event.x;
toggleTransition(false); toggleTransition(false);
@@ -213,30 +197,6 @@ export default function(svg, ui, methods) {
methods.onUpdateTimerange(scale.x.domain()); methods.onUpdateTimerange(scale.x.domain());
} }
const drag = d3.drag()
.on('start', onDragStart)
.on('drag', onDrag)
.on('end', onDragEnd);
/**
* Updates data displayed by this timeline, but only render if necessary
* @param {Object} domain: Redux state domain subtree
* @param {Object} app: Redux state app subtree
*/
function updateAxis() {
let groupYs = Array.apply(null, Array(categories.length));
groupYs = groupYs.map((g, i) => {
return (i + 1) * HEIGHT / groupYs.length;
});
scale.y = d3.scaleOrdinal()
.domain(categories)
.range(groupYs);
axis.y =
d3.axisLeft(scale.y)
.tickValues(categories.map(c => c.category));
}
/** /**
* Render axis on timeline and viewbox boundaries * Render axis on timeline and viewbox boundaries
@@ -251,27 +211,6 @@ export default function(svg, ui, methods) {
.transition() .transition()
.duration(transitionDuration) .duration(transitionDuration)
.call(axis.x1); .call(axis.x1);
axis.y.tickSize(WIDTH - margin.left);
if (!dom.axis.dragGrabber) {
dom.axis.dragGrabber = dom.svg.insert('rect', ':first-child')
.attr('class', 'drag-grabber')
.attr('x', margin.left)
.attr('y', margin.top)
.attr('width', WIDTH - margin.left)
.attr('height', HEIGHT)
.call(drag);
}
if (!dom.axis.y) {
dom.axis.y = dom.svg.insert('g', ':first-child')
.attr('transform', `translate(${WIDTH}, 0)`)
.attr('class', 'yAxis');
}
dom.axis.y
.call(axis.y);
} }
@@ -289,15 +228,16 @@ export default function(svg, ui, methods) {
} }
function render() { function render() {
updateAxis();
renderAxis(); renderAxis();
} }
return { return {
getEventX, getEventX,
getEventY,
applyZoom, applyZoom,
moveTime, moveTime,
update update,
onDragStart,
onDrag,
onDragEnd
}; };
} }

View File

@@ -176,6 +176,12 @@
cursor: -webkit-grab; cursor: -webkit-grab;
cursor: -moz-grab; cursor: -moz-grab;
} }
.tick text {
font-size: 10px;
font-family: 'Lato';
text-anchor: end;
}
} }
.drag-grabber { .drag-grabber {