Commit 373e97a9 authored by alain's avatar alain 💾

add optional graph pages

parent ed799c1c
//const path = require('path');
const { editWebpackPlugin, appendWebpackPlugin } = require('@rescripts/utilities')
const CopyWebpackPlugin = require('copy-webpack-plugin');
//const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
......@@ -10,17 +11,19 @@ module.exports = config => {
)
config.resolve.plugins.splice(scopePluginIndex, 1)
config = appendWebpackPlugin(
new CopyWebpackPlugin({
patterns: [
{ from: '../public', to: '../build' }
{
from: '../public',
to: '../build'
}
]
}),
config,
config
)
// config = appendWebpackPlugin(
// new BundleAnalyzerPlugin(),
// config,
......@@ -40,7 +43,7 @@ module.exports = config => {
config,
)
// set js filename
// set js filename
config.output.filename = 'bundle.js'
// disable sourcemap
......@@ -56,7 +59,6 @@ module.exports = config => {
}
return config
}
import React from 'react'
export const appSettingsDefault = {
language: "nl",
header: false,
panelBreakpoint: 600,
yAxisWidth: 30,
defaultGranularity: "hourly"
defaultGranularity: "hourly",
mapRoute: "/"
}
This diff is collapsed.
......@@ -31,11 +31,11 @@ class Modal extends React.Component {
return (
<div className="modal" onClick={event => {
// Prevent closing on double click / touch devices (it's registering a click event for some reason..)
if(+ new Date() - this.mountTime > 300) closeModal(event)
if(closeModal && + new Date() - this.mountTime > 300) closeModal(event)
}}>
<div className="content" onClick={event => { event.stopPropagation() }}>
<button className="button-close" onClick={event => closeModal(event)}>×</button>
{this.props.children}
{ closeModal && <button className="button-close" onClick={event => closeModal(event)}>×</button>}
{ this.props.children }
</div>
</div>
)
......
......@@ -256,7 +256,7 @@ class StationInfo extends React.Component {
</span> }
</header>
{ downloadJSX }
<div id="chart-body">
<div id="chart-body" className="station-chart">
<ResponsiveContainer width="100%" height={chartHeight}>
<ComposedChart data={data} margin={{ top: 0, right: 30, left: 0, bottom: 10 }}>
{ gradients }
......
import React from 'react'
import { Link } from 'react-router-dom'
import { appSettings } from "../../../config/app"
const Header = (props) => {
return (
<header id="site-header">
<Link id="site-titles" to="/">
<h1>{ appSettings.header.title }</h1>
<h2>{ appSettings.header.subtitle }</h2>
</Link>
<ul id="site-navigation">
{ Object.keys(appSettings.header.navigation).map(i => {
const iValue = appSettings.header.navigation[i]
if(typeof iValue === 'string' || iValue instanceof String) {
return <li key={i}>
<Link to={iValue}>{i}</Link>
</li>
} else {
return <li key={i}>
<span>{i}</span>
<div className="sub">
<ul>
{ Object.keys(iValue).map(j => <li key={j}><Link to={iValue[j]}>{ j }</Link></li>) }
</ul>
</div>
</li>
}
})}
</ul>
</header>
)
}
export default Header
\ No newline at end of file
import React from 'react'
import ReScatterChart from './ReScatterChart'
import ReLineChart from './ReLineChart'
import ReAreaChart from './ReAreaChart'
import ReBarChart from './ReBarChart'
const Page = (props) => {
return (
<div className="page">
<h1>{props.title}</h1>
<div dangerouslySetInnerHTML={{__html: props.intro}} />
{ props.content.map(item => {
switch (item.type) {
case 'scatter-chart':
return <ReScatterChart key={item.title} title={item.title} data={item.data} settings={item.settings} />
case 'line-chart':
return <ReLineChart key={item.title} title={item.title} data={item.data} settings={item.settings} />
case 'area-chart':
return <ReAreaChart key={item.title} title={item.title} data={item.data} settings={item.settings} />
case 'bar-chart':
return <ReBarChart key={item.title} title={item.title} data={item.data} settings={item.settings} />
default:
return <div>...</div>
}
}
)}
</div>
)
}
export default Page
\ No newline at end of file
import React, { useState, useEffect } from 'react';
import { ResponsiveContainer, AreaChart, Area, XAxis, YAxis, Tooltip, Legend } from "recharts"
import { appSettings } from "../../../config/app"
const ReLineChart = (props) => {
const settings = props.settings
const [data, setData] = useState(null)
useEffect(() => {
async function fetchData() {
const response = await fetch(props.settings.data)
const json = await response.json()
setData(json)
}
fetchData();
}, [props.settings.data]);
return (
<div>
<h2>{props.title}</h2>
<ResponsiveContainer width="100%" height={320}>
<AreaChart data={data} margin={{ top: 5, right: 15, bottom: 20, left: 0 }}>
<XAxis { ...settings.x } />
<YAxis label={{ value: settings.y.name, angle: -90, position: 'insideBottomLeft', offset:10, style: {textAnchor: 'start'}}} { ...settings.y } />
<Tooltip cursor={{ strokeDasharray: '3 3' }} animationDuration={0} { ...settings.tooltip } />
<Legend align="right" { ...settings.legend } />
{ settings.categories.map((category, i) => {
return <Area type="linear" stackId="1" key={category} dataKey={category} fill={appSettings.colors[i]} fillOpacity="1" stroke={appSettings.colors[i]} />
}) }
</AreaChart>
</ResponsiveContainer>
</div>
)
}
export default ReLineChart
\ No newline at end of file
import React, { useState, useEffect } from 'react';
import { ResponsiveContainer, BarChart, Bar, XAxis, YAxis, Tooltip, Legend } from "recharts"
import { appSettings } from "../../../config/app"
const CustomizedAxisTick = (props) => {
const { x, y, payload } = props
const words = payload.value ? payload.value.replace('_answer','').split('_') : []
return (
<g transform={`translate(${x},${y})`}>
{ words.map((word, i) => <text key={i} x={0} y={0} dy={14*(i+1)} textAnchor="middle">{word}</text>) }
</g>
)
}
const ReLineChart = (props) => {
const settings = props.settings
const [data, setData] = useState(null)
useEffect(() => {
async function fetchData() {
const response = await fetch(props.settings.data)
const json = await response.json()
setData(json)
}
fetchData();
}, [props.settings.data]);
return (
<div>
<h2>{props.title}</h2>
<ResponsiveContainer width="100%" height={320}>
<BarChart data={data} margin={{ top: 5, right: 15, bottom: 20, left: 0 }}>
<XAxis { ...settings.x } tick={CustomizedAxisTick} minTickGap={0} interval={0} />
<YAxis label={{ value: settings.y.name, angle: -90, position: 'insideBottomLeft', offset:10, style: {textAnchor: 'start'}}} { ...settings.y } />
<Tooltip cursor={{ strokeDasharray: '3 3' }} animationDuration={0} />
<Legend align="right" wrapperStyle={{bottom: 0}} { ...settings.legend } />
{ settings.categories.map((category, i) => {
return <Bar type="linear" key={category} dataKey={category} fill={appSettings.colorsBinary[i]} />
}) }
</BarChart>
</ResponsiveContainer>
</div>
)
}
export default ReLineChart
\ No newline at end of file
import React, { useState, useEffect } from 'react';
import { ResponsiveContainer, LineChart, Line, XAxis, YAxis, Tooltip, Legend } from "recharts"
import { appSettings } from "../../../config/app"
const ReLineChart = (props) => {
const settings = props.settings
const [data, setData] = useState(null)
useEffect(() => {
async function fetchData() {
const response = await fetch(props.settings.data)
const json = await response.json()
setData(json)
}
fetchData();
}, [props.settings.data]);
return (
<div>
<h2>{props.title}</h2>
<ResponsiveContainer width="100%" height={320}>
<LineChart data={data} margin={{ top: 5, right: 15, bottom: 20, left: 0 }}>
<XAxis label={{ value: settings.x.name, position: 'insideBottomLeft', offset:-18, style: {textAnchor: 'start'}}} { ...settings.x } />
<YAxis label={{ value: settings.y.name, angle: -90, position: 'insideBottomLeft', offset:10, style: {textAnchor: 'start'}}} { ...settings.y } />
<Tooltip cursor={{ strokeDasharray: '3 3' }} animationDuration={0} />
<Legend align="right" { ...settings.legend } />
{ settings.categories.map((category, i) => {
return <Line type="linear" key={category} dataKey={category} stroke={appSettings.colors[i]} />
}) }
</LineChart>
</ResponsiveContainer>
</div>
)
}
export default ReLineChart
\ No newline at end of file
import React, { useState, useEffect } from 'react';
import { ResponsiveContainer, ScatterChart, Scatter, XAxis, YAxis, ZAxis, Tooltip, Legend } from "recharts"
import { appSettings } from "../../../config/app"
const ReScatterChart = (props) => {
const settings = props.settings
const [data, setData] = useState({});
useEffect(() => {
async function fetchData() {
const response = await fetch(props.settings.data);
const json = await response.json();
// Object.keys(json).forEach(c => {
// json[c] = getRandomSubarray(json[c], 1000)
// })
setData(json);
}
fetchData();
}, [props.settings.data]);
return (
<div>
<h2>{props.title}</h2>
<ResponsiveContainer width="100%" height={320}>
<ScatterChart margin={{ top: 5, right: 15, bottom: 20, left: 0 }}>
<XAxis label={{ value: settings.x.name, position: 'insideBottomLeft', offset:-18, style: {textAnchor: 'start'}}} { ...settings.x } />
<YAxis label={{ value: settings.y.name, angle: -90, position: 'insideBottomLeft', offset:10, style: {textAnchor: 'start'}}} { ...settings.y } />
<ZAxis dataKey="c" type="number" range={[5,150]} />
<Tooltip cursor={{ strokeDasharray: '3 3' }} animationDuration={0} />
<Legend align="right" />
{Object.keys(data).map((category, i) => <Scatter key={category} name={category} data={data[category]} fill={appSettings.colors[i]} opacity="0.7" />)}
</ScatterChart>
</ResponsiveContainer>
</div>
)
}
export default ReScatterChart
\ No newline at end of file
......@@ -105,7 +105,7 @@ class PanelLayers extends React.Component {
</span>
}
{ parameter.tooltip && <Tooltip parameter={parameter} /> }
{ unit && <Legend unit={unit} width={this.container.current.offsetWidth} /> }
{ unit && <Legend unit={unit} width={this.container.current ? this.container.current.offsetWidth : 0} /> }
</li>)
})
}
......
$header-height: 3rem;
#app.with-header {
position: absolute;
height: calc(100% - #{$header-height});
bottom: 0;
overflow: initial;
}
#site-header {
position: absolute;
bottom: 100%;
width: 100%;
height: $header-height;
padding: 0 0.6rem;
background-color: #FFF;
box-shadow: 0 0 5px 0 rgba(0, 0, 0, 0.15);
z-index: 1300;
display: flex;
justify-content: space-between;
}
#site-titles {
display: flex;
align-items: baseline;
text-decoration: none;
h1 {
line-height: $header-height;
margin: 0 0.5rem 0 0;
}
h2 {
line-height: $header-height;
margin: 0;
font-size: 1.2rem;
}
}
#site-navigation {
display: flex;
align-items: center;
align-items: flex-start;
li {
position: relative;
margin: 0 1rem;
a, span {
display: inline-block;
line-height: $header-height;
text-decoration: none;
}
span {
cursor: default;
}
a {
text-decoration: none;
&:hover {
text-decoration: underline;
}
}
.sub {
display: none;
position: absolute;
right: 0;
padding-top: 0.5rem;
}
&:hover .sub {
display: block;
}
ul {
box-shadow: 0 0 5px 0 rgba(0, 0, 0, 0.15);
li {
margin: 0;
background-color: #FFF;
a {
line-height: 2.5em;
padding: 0 0.75em;
}
}
&:before {
content: "";
position: absolute;
bottom: calc(100% - 0.5rem);
right: 0.5rem;
width: 0;
height: 0;
border-left: 5px solid transparent;
border-right: 5px solid transparent;
border-bottom: 5px solid #FFF;
}
}
}
}
\ No newline at end of file
......@@ -4,7 +4,7 @@
left: 0;
bottom: 0;
right: 0;
padding: 0.5rem;
padding: 0.75rem;
background: rgba(0, 0, 0, 0.15);
overflow: auto;
text-align: center;
......
@mixin line-on-area { stroke-width: 1px; stroke-dasharray: 2px; }
@mixin line-on-area-primary {
@include line-on-area;
stroke: #000;
}
@mixin line-on-area-secondary {
@include line-on-area;
stroke: #bbb;
}
@mixin line { stroke-width: 1.5px; }
@mixin line-primary {
@include line;
stroke: url(#yaxis);
}
@mixin line-secondary {
@include line;
stroke: #ddd;
}
#chart-header {
display: flex;
flex-wrap: wrap;
......@@ -110,8 +133,18 @@
right: 0;
}
}
path.line.primary { @include line-primary; stroke: url(#legend); }
path.line.secondary { @include line-secondary; }
path.line-on-area.primary { @include line-on-area-primary; }
path.line-on-area.secondary { @include line-on-area-secondary; }
path.area.primary { fill: url(#legend); fill-opacity: 0.65; }
path.area.secondary { fill: #ddd; }
}
.recharts-wrapper {
user-select: none;
}
......@@ -137,9 +170,6 @@
}
}
.yAxis .recharts-cartesian-axis-line {
transform: translateX(3px);
}
rect.recharts-brush-slide {
fill: #000;
......@@ -170,54 +200,30 @@ rect.recharts-brush-slide {
}
@mixin line-on-area { stroke-width: 1px; stroke-dasharray: 2px; }
@mixin line-on-area-primary {
@include line-on-area;
stroke: #000;
}
@mixin line-on-area-secondary {
@include line-on-area;
stroke: #bbb;
}
@mixin line { stroke-width: 1.5px; }
@mixin line-primary {
@include line;
stroke: url(#yaxis);
}
@mixin line-secondary {
@include line;
stroke: #ddd;
}
.recharts-line {
&.line {
&.primary path { @include line-primary; }
&.secondary path { @include line-secondary; }
}
&.line-on-area {
&.primary path { @include line-on-area-primary; }
&.secondary path { @include line-on-area-secondary; }
.station-chart {
.yAxis .recharts-cartesian-axis-line {
transform: translateX(3px);
}
}
.recharts-area {
path { stroke: none; fill-opacity: 0.5; }
.recharts-line {
&.line {
&.primary path { @include line-primary; }
&.secondary path { @include line-secondary; }
}
&.line-on-area {
&.primary path { @include line-on-area-primary; }
&.secondary path { @include line-on-area-secondary; }
}
}
&.primary path.recharts-area-area { fill: url(#yaxis); }
&.secondary path.recharts-area-area { fill: #ddd; }
.recharts-area {
path { stroke: none; fill-opacity: 0.5; }
&.primary path.recharts-area-area { fill: url(#yaxis); }
&.secondary path.recharts-area-area { fill: #ddd; }
}
}
#chart-data-selection {
path.line.primary { @include line-primary; stroke: url(#legend); }
path.line.secondary { @include line-secondary; }
path.line-on-area.primary { @include line-on-area-primary; }
path.line-on-area.secondary { @include line-on-area-secondary; }
path.area.primary { fill: url(#legend); fill-opacity: 0.65; }
path.area.secondary { fill: #ddd; }
}
.recharts-active-dot circle {
......
......@@ -171,7 +171,26 @@ button.button-text {
cursor: pointer;
&:hover {
opacity: 0.9;
opacity: 0.85;
}
}
a.btn {
display: inline-block;
border: none;
background-color: var(--color-button, #000000);
color: #FFF;
font-size: 0.9rem;
line-height: 1.8rem;
height: 1.8rem;
margin: 0 0.25rem 0.25rem 0;
padding: 0 0.5em;
cursor: pointer;
text-decoration: none;
&:hover {
opacity: 0.85;
}
}
......
......@@ -7,6 +7,7 @@ body {
#root {
@import "typography";
@import "ui";
@import "header";
@import "panel";
@import "modal";
@import "tooltip";
......@@ -18,6 +19,7 @@ body {
height: 100%;
font-size: 16px;
background-color: green;
#app {
position: relative;
......@@ -26,6 +28,10 @@ body {
overflow: hidden;
}
.page .recharts-responsive-container {
margin-bottom: 4rem;
}
img {
max-width: 100%;
height: auto;
......
import React from 'react'
import ReactDOM from 'react-dom'
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'
import { createStore } from 'redux'
import { Provider, connect } from 'react-redux'
......@@ -12,6 +13,7 @@ import '../../config/style.css'
import "moment/locale/nl-be"
import { texts } from "../../config/texts"
import { appSettings } from "../../config/app"
import { initialLayers, dataGroups } from "../../config/data.js"
import { mapDefaults, mapLocations, mapControlSettings, mapStyle } from "../../config/map.js"
......@@ -33,6 +35,11 @@ import PanelLayers from "./Panels/PanelLayers"
import Modal from "./Modal/Modal.js"
import Header from 'Page/Header.js'
import Page from 'Page/Page.js'