mirror of
https://github.com/bellingcat/ukraine-timemap.git
synced 2026-06-11 21:08:36 +03:00
WIP: better timeline styles
This commit is contained in:
@@ -38,11 +38,8 @@ class Dashboard extends React.Component {
|
||||
this.props.actions.fetchDomain()
|
||||
.then(domain => this.props.actions.updateDomain(domain))
|
||||
.then(({ domain }) => {
|
||||
// modify trackHeight according to number of categories
|
||||
if (domain.categories.length === 1) {
|
||||
this.props.actions.updateDimensions({ trackHeight: 30 })
|
||||
} else if (domain.categories.length >= 4) {
|
||||
this.props.actions.updateDimensions({ margin_top: 0, trackHeight: 90 })
|
||||
if (domain.categories.length >= 4) {
|
||||
this.props.actions.updateDimensions({ marginTop: 0 })
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -63,6 +63,7 @@ class Map extends React.Component {
|
||||
}
|
||||
|
||||
initializeMap () {
|
||||
return
|
||||
/**
|
||||
* Creates a Leaflet map and a tilelayer for the map background
|
||||
*/
|
||||
|
||||
@@ -25,7 +25,7 @@ class Timeline extends React.Component {
|
||||
this.svgRef = React.createRef()
|
||||
this.state = {
|
||||
isFolded: false,
|
||||
dims: props.app.timeline.dimensions,
|
||||
dims: props.dimensions,
|
||||
scaleX: null,
|
||||
scaleY: null,
|
||||
timerange: [null, null],
|
||||
@@ -47,14 +47,14 @@ class Timeline extends React.Component {
|
||||
})
|
||||
}
|
||||
|
||||
if ((hash(nextProps.domain.categories) !== hash(this.props.domain.categories)) || hash(nextProps.app.timeline.dimensions) != hash(this.props.app.timeline.dimensions)) {
|
||||
const { trackHeight, margin_top } = nextProps.app.timeline.dimensions
|
||||
if ((hash(nextProps.domain.categories) !== hash(this.props.domain.categories)) || hash(nextProps.dimensions) != hash(this.props.dimensions)) {
|
||||
const { trackHeight, marginTop } = nextProps.dimensions
|
||||
this.setState({
|
||||
scaleY: this.makeScaleY(nextProps.domain.categoriesWithTimeline, trackHeight, margin_top )
|
||||
scaleY: this.makeScaleY(nextProps.domain.categoriesWithTimeline, trackHeight, marginTop )
|
||||
})
|
||||
}
|
||||
|
||||
if (nextProps.app.timeline.dimensions.trackHeight !== this.props.app.timeline.dimensions.trackHeight) {
|
||||
if (nextProps.dimensions.trackHeight !== this.props.dimensions.trackHeight) {
|
||||
this.computeDims()
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ class Timeline extends React.Component {
|
||||
makeScaleX () {
|
||||
return d3.scaleTime()
|
||||
.domain(this.state.timerange)
|
||||
.range([this.state.dims.margin_left, this.state.dims.width - this.state.dims.width_controls])
|
||||
.range([this.state.dims.marginLeft, this.state.dims.width - this.state.dims.width_controls])
|
||||
}
|
||||
|
||||
makeScaleY (categories, trackHeight, marginTop) {
|
||||
@@ -119,7 +119,7 @@ class Timeline extends React.Component {
|
||||
|
||||
this.setState({
|
||||
dims: {
|
||||
...this.props.app.timeline.dimensions,
|
||||
...this.props.dimensions,
|
||||
width: boundingClient.width
|
||||
}
|
||||
},
|
||||
@@ -180,15 +180,16 @@ class Timeline extends React.Component {
|
||||
onApplyZoom (zoom) {
|
||||
const extent = this.getTimeScaleExtent()
|
||||
const newCentralTime = d3.timeMinute.offset(this.state.scaleX.domain()[0], extent / 2)
|
||||
const { rangeLimits } = this.props.app.timeline
|
||||
|
||||
let newDomain0 = d3.timeMinute.offset(newCentralTime, -zoom.duration / 2)
|
||||
let newDomainF = d3.timeMinute.offset(newCentralTime, zoom.duration / 2)
|
||||
|
||||
if (this.props.app.timeline.rangeLimits) {
|
||||
if (rangeLimits) {
|
||||
// If the store contains absolute time limits,
|
||||
// make sure the zoom doesn't go over them
|
||||
const minDate = parseDate(this.props.app.timeline.rangeLimits[0])
|
||||
const maxDate = parseDate(this.props.app.timeline.rangeLimits[1])
|
||||
const minDate = parseDate(rangeLimits[0])
|
||||
const maxDate = parseDate(rangeLimits[1])
|
||||
|
||||
if (newDomain0 < minDate) {
|
||||
newDomain0 = minDate
|
||||
@@ -232,15 +233,15 @@ class Timeline extends React.Component {
|
||||
const dragNow = this.state.scaleX.invert(d3.event.x).getTime()
|
||||
const timeShift = (drag0 - dragNow) / 1000
|
||||
|
||||
const { range } = this.props.app.timeline
|
||||
const { range, rangeLimits } = this.props.app.timeline
|
||||
let newDomain0 = d3.timeSecond.offset(range[0], timeShift)
|
||||
let newDomainF = d3.timeSecond.offset(range[1], timeShift)
|
||||
|
||||
if (this.props.app.timeline.rangeLimits) {
|
||||
if (rangeLimits) {
|
||||
// If the store contains absolute time limits,
|
||||
// make sure the zoom doesn't go over them
|
||||
const minDate = parseDate(this.props.app.timeline.rangeLimits[0])
|
||||
const maxDate = parseDate(this.props.app.timeline.rangeLimits[1])
|
||||
const minDate = parseDate(rangeLimits[0])
|
||||
const maxDate = parseDate(rangeLimits[1])
|
||||
|
||||
newDomain0 = (newDomain0 < minDate) ? minDate : newDomain0
|
||||
newDomainF = (newDomainF > maxDate) ? maxDate : newDomainF
|
||||
@@ -280,9 +281,13 @@ class Timeline extends React.Component {
|
||||
let classes = `timeline-wrapper ${(this.state.isFolded) ? ' folded' : ''}`
|
||||
classes += (app.narrative !== null) ? ' narrative-mode' : ''
|
||||
const { dims } = this.state
|
||||
const foldedStyle = { bottom: this.state.isFolded ? -dims.height : 0 }
|
||||
const heightStyle = {height: dims.height}
|
||||
const extraStyle = { ...heightStyle, ...foldedStyle }
|
||||
const contentHeight = {height: dims.contentHeight}
|
||||
|
||||
return (
|
||||
<div className={classes}>
|
||||
<div className={classes} style={extraStyle}>
|
||||
<Header
|
||||
title={copy[this.props.app.language].timeline.info}
|
||||
date0={formatterWithYear(this.state.timerange[0])}
|
||||
@@ -290,12 +295,12 @@ class Timeline extends React.Component {
|
||||
onClick={() => { this.onClickArrow() }}
|
||||
hideInfo={isNarrative}
|
||||
/>
|
||||
<div className='timeline-content'>
|
||||
<div id={this.props.ui.dom.timeline} className='timeline'>
|
||||
<div className='timeline-content' style={heightStyle}>
|
||||
<div id={this.props.ui.dom.timeline} className='timeline' style={contentHeight} >
|
||||
<svg
|
||||
ref={this.svgRef}
|
||||
width={dims.width}
|
||||
height={dims.height}
|
||||
style={contentHeight}
|
||||
>
|
||||
<Clip
|
||||
dims={dims}
|
||||
@@ -354,6 +359,7 @@ class Timeline extends React.Component {
|
||||
|
||||
function mapStateToProps (state) {
|
||||
return {
|
||||
dimensions: selectors.selectDimensions(state),
|
||||
isNarrative: !!state.app.narrative,
|
||||
domain: {
|
||||
datetimes: selectors.selectDatetimes(state),
|
||||
@@ -365,7 +371,7 @@ function mapStateToProps (state) {
|
||||
selected: state.app.selected,
|
||||
language: state.app.language,
|
||||
timeline: state.app.timeline,
|
||||
narrative: state.app.narrative
|
||||
narrative: state.app.narrative,
|
||||
},
|
||||
ui: {
|
||||
dom: state.ui.dom,
|
||||
|
||||
@@ -38,7 +38,7 @@ class TimelineAxis extends React.Component {
|
||||
this.x1 =
|
||||
d3.axisBottom(this.props.scaleX)
|
||||
.ticks(10)
|
||||
.tickPadding(this.props.dims.margin_top)
|
||||
.tickPadding(this.props.dims.marginTop)
|
||||
.tickSize(0)
|
||||
.tickFormat(d3.timeFormat(sndFmt))
|
||||
|
||||
@@ -63,13 +63,13 @@ class TimelineAxis extends React.Component {
|
||||
<React.Fragment>
|
||||
<g
|
||||
ref={this.xAxis0Ref}
|
||||
transform={`translate(0, 25)`}
|
||||
transform={`translate(0, 15)`}
|
||||
clipPath={`url(#clip)`}
|
||||
className={`axis xAxis`}
|
||||
/>
|
||||
<g
|
||||
ref={this.xAxis1Ref}
|
||||
transform={`translate(0, 105)`}
|
||||
transform={`translate(0, ${this.props.dims.trackHeight + 35})`}
|
||||
clipPath={`url(#clip)`}
|
||||
className={`axis axisHourText`}
|
||||
/>
|
||||
|
||||
@@ -26,7 +26,7 @@ class TimelineCategories extends React.Component {
|
||||
|
||||
renderCategory (category, idx) {
|
||||
const dims = this.props.dims
|
||||
const strokeWidth = dims.trackHeight / (this.props.categories.length + 1)
|
||||
const strokeWidth = 1 //dims.trackHeight / (this.props.categories.length + 1)
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
@@ -36,10 +36,10 @@ class TimelineCategories extends React.Component {
|
||||
opacity='0.5'
|
||||
transform={`translate(0,${this.props.getCategoryY(category.category)})`}
|
||||
>
|
||||
<line x1={dims.margin_left} x2={dims.width - dims.width_controls} />
|
||||
<line x1={dims.marginLeft} x2={dims.width - dims.width_controls} />
|
||||
</g>
|
||||
<g class='tick' opacity='1' transform={`translate(0,${this.props.getCategoryY(category.category)})`}>
|
||||
<text x={dims.margin_left - 5} dy='0.32em'>{category.category}</text>
|
||||
<text x={dims.marginLeft - 5} dy='0.32em'>{category.category}</text>
|
||||
</g>
|
||||
</React.Fragment>
|
||||
)
|
||||
@@ -54,10 +54,10 @@ class TimelineCategories extends React.Component {
|
||||
<rect
|
||||
ref={this.grabRef}
|
||||
class='drag-grabber'
|
||||
x={dims.margin_left}
|
||||
y={dims.margin_top}
|
||||
width={dims.width - dims.margin_left - dims.width_controls}
|
||||
height={dims.trackHeight}
|
||||
x={dims.marginLeft}
|
||||
y={dims.marginTop}
|
||||
width={dims.width - dims.marginLeft - dims.width_controls}
|
||||
height='100%'
|
||||
/>
|
||||
</g>
|
||||
)
|
||||
|
||||
@@ -3,10 +3,10 @@ import React from 'react'
|
||||
const TimelineClip = ({ dims }) => (
|
||||
<clipPath id='clip'>
|
||||
<rect
|
||||
x={dims.margin_left}
|
||||
x={dims.marginLeft}
|
||||
y='0'
|
||||
width={dims.width - dims.margin_left - dims.width_controls}
|
||||
height={dims.height - 25}
|
||||
width={dims.width - dims.marginLeft - dims.width_controls}
|
||||
height={dims.contentHeight}
|
||||
/>
|
||||
</clipPath>
|
||||
)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react'
|
||||
|
||||
export default ({
|
||||
const actual = ({
|
||||
category,
|
||||
events,
|
||||
x,
|
||||
@@ -21,3 +21,5 @@ export default ({
|
||||
height={height}
|
||||
/>
|
||||
)
|
||||
|
||||
export default () => null
|
||||
|
||||
@@ -98,7 +98,7 @@ const TimelineEvents = ({
|
||||
category={dot.category}
|
||||
events={unlocatedEvents}
|
||||
x={getDatetimeX(datetime)}
|
||||
y={dims.margin_top}
|
||||
y={dims.marginTop}
|
||||
width={(2 * sizes.eventDotR) * 0.9}
|
||||
height={dims.trackHeight}
|
||||
styleProps={unlocatedProps}
|
||||
|
||||
@@ -4,14 +4,14 @@ const TimelineHandles = ({ dims, onMoveTime }) => {
|
||||
return (
|
||||
<g className='time-controls-inline'>
|
||||
<g
|
||||
transform={`translate(${dims.margin_left - 20}, 120)`}
|
||||
transform={`translate(${dims.marginLeft - 20}, ${dims.contentHeight - 10})`}
|
||||
onClick={() => onMoveTime('backwards')}
|
||||
>
|
||||
<circle r='15' />
|
||||
<path d='M0,-7.847549217020565L6.796176979388489,3.9237746085102825L-6.796176979388489,3.9237746085102825Z' transform='rotate(270)' />
|
||||
</g>
|
||||
<g
|
||||
transform={`translate(${dims.width - dims.width_controls + 20}, 120)`}
|
||||
transform={`translate(${dims.width - dims.width_controls + 20}, ${dims.contentHeight - 10})`}
|
||||
onClick={() => onMoveTime('forward')}
|
||||
>
|
||||
<circle r='15' />
|
||||
|
||||
@@ -7,8 +7,8 @@ const TimelineLabels = ({ dims, timelabels }) => {
|
||||
<g>
|
||||
<line
|
||||
class='axisBoundaries'
|
||||
x1={dims.margin_left}
|
||||
x2={dims.margin_left}
|
||||
x1={dims.marginLeft}
|
||||
x2={dims.marginLeft}
|
||||
y1='10'
|
||||
y2='20'
|
||||
/>
|
||||
|
||||
@@ -35,7 +35,7 @@ const TimelineMarkers = ({
|
||||
<rect
|
||||
className='timeline-marker'
|
||||
x={0}
|
||||
y={-dims.margin_top - (noCategories > 2 ? noCategories * MARKER_DISPLACED : MARKER_DISPLACED)}
|
||||
y={-dims.marginTop - (noCategories > 2 ? noCategories * MARKER_DISPLACED : MARKER_DISPLACED)}
|
||||
width={(2 * sizes.eventDotR) * 0.9}
|
||||
height={dims.trackHeight}
|
||||
stroke={styles ? styles.stroke : colors.primaryHighlight}
|
||||
|
||||
@@ -15,7 +15,6 @@ $timeline-height: 170px;
|
||||
|
||||
&.folded {
|
||||
transition: bottom 0.2s ease;
|
||||
bottom: -$timeline-height;
|
||||
|
||||
.timeline-header .timeline-toggle p .arrow-down {
|
||||
transform: translate(0, 5px)rotate(-135deg);
|
||||
@@ -95,7 +94,9 @@ $timeline-height: 170px;
|
||||
}
|
||||
|
||||
.timeline-content {
|
||||
height: 160px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding-top: 20px;
|
||||
|
||||
.timeline-labels {
|
||||
|
||||
@@ -26,6 +26,7 @@ export const getTagTree = state => state.domain.tags
|
||||
export const getActiveTags = state => state.app.filters.tags
|
||||
export const getActiveCategories = state => state.app.filters.categories
|
||||
export const getTimeRange = state => state.app.timeline.range
|
||||
export const getTimelineDimensions = state => state.app.timeline.dimensions
|
||||
export const selectNarrative = state => state.app.narrative
|
||||
|
||||
/**
|
||||
@@ -205,3 +206,13 @@ export const selectCategoriesWithTimeline = createSelector(
|
||||
return categories.filter(cat => hasLocated[cat.category])
|
||||
}
|
||||
)
|
||||
|
||||
export const selectDimensions = createSelector(
|
||||
[getTimelineDimensions],
|
||||
(dimensions) => {
|
||||
return {
|
||||
...dimensions,
|
||||
trackHeight: dimensions.contentHeight - 50 // height of time labels
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@@ -60,13 +60,13 @@ const initial = {
|
||||
},
|
||||
timeline: {
|
||||
dimensions: {
|
||||
height: 140,
|
||||
height: 400,
|
||||
width: 0,
|
||||
marginLeft: 100,
|
||||
marginTop: 15,
|
||||
marginBottom: 60,
|
||||
contentHeight: 200,
|
||||
width_controls: 100,
|
||||
height_controls: 115,
|
||||
margin_left: 100,
|
||||
margin_top: 15,
|
||||
trackHeight: 60
|
||||
},
|
||||
range: [
|
||||
new Date(2001, 2, 23, 12),
|
||||
@@ -86,7 +86,7 @@ const initial = {
|
||||
isFetchingSources: false,
|
||||
isCover: true,
|
||||
isCardstack: true,
|
||||
isInfopopup: true,
|
||||
isInfopopup: false,
|
||||
isShowingSites: true
|
||||
},
|
||||
cover: {
|
||||
|
||||
Reference in New Issue
Block a user