From ef0e6dda737f191ef369109311fec590dcd0c408 Mon Sep 17 00:00:00 2001 From: Roald Christesen Date: Wed, 4 Oct 2023 14:46:25 +0200 Subject: [PATCH 1/8] introducing new components numView and text and implemented view update funcionality --- src/app.js | 96 +++++++++++++++----- src/appModel.js | 65 +++++++++++-- src/components/button.js | 1 - src/components/demoComponent.js | 116 +++++++++++++++++++++--- src/components/numView.js | 88 ++++++++++++++++++ src/components/text.js | 41 +++++++++ src/components/vBarsView.js | 41 --------- src/index.html | 11 ++- src/index.js | 9 +- src/main.css | 36 +++++++- src/main.js | 156 +++++++++++++++++++------------- 11 files changed, 500 insertions(+), 160 deletions(-) create mode 100644 src/components/numView.js create mode 100644 src/components/text.js delete mode 100644 src/components/vBarsView.js diff --git a/src/app.js b/src/app.js index ceb5a20..ae0a7f3 100644 --- a/src/app.js +++ b/src/app.js @@ -1,4 +1,4 @@ -import { View, Controller } from '@sndcds/mvc' +import { Controller, Component } from '@sndcds/mvc' export default class App extends Controller { @@ -28,7 +28,29 @@ export default class App extends Controller { } } - renderAgeSection() { + updateView() { + console.log(`

${this.model.districtName()}

`) + + this.setProperties('text-distict-details', { 'html': `

Stadtteil: ${this.model.districtName()}

` }) + + const residentsInDestrict = this.model.districtData.valueByPath(['district_detail', '2021', 'residents']) + const residentsTotal = this.model.residentsTotal() + const percent = residentsInDestrict / residentsTotal * 100 // TODO + + this.setProperties('residents-in-destrict', { 'value': this.formatNumber(residentsInDestrict) }) + this.setProperties('residents-percent-in-destrict', { 'value': this.formatNumber(percent) }) + this.setProperties('residents-total', { 'value': this.formatNumber(residentsTotal) }) + + this.updateViewAgeSection() + this.updateViewAgeSection2() + this.updateSectionBirths() + + + const c = this.componentById('svg') + c.setProperties({ 'values': this.model.residentsInDistrictsArray() }) + } + + updateViewAgeSection() { const items = [ { 'id': 'age-view-1', 'path': ['district_detail', '2021', 'age_groups', 'age_to_under_18'] }, { 'id': 'age-view-2', 'path': ['district_detail', '2021', 'age_groups', 'age_18_to_under_30'] }, @@ -42,13 +64,13 @@ export default class App extends Controller { let sum = 0 items.forEach((item) => { - sum += this.getNestedValue(this.model.districtObject, item.path) + sum += this.model.districtData.valueByPath(item.path) }) let barOffset = 0 items.forEach((item) => { const c = this.componentById(item.id) - const d = this.getNestedValue(this.model.districtObject, item.path) + const d = this.model.districtData.valueByPath(item.path) const percentage = d / sum * 100 c.setProperties( { @@ -60,34 +82,66 @@ export default class App extends Controller { }) } + updateViewAgeSection2() { + const items = [ + { 'id': 'residents-0-18', 'path': ['district_detail', '2021', 'age_groups', 'age_to_under_18'] }, + { 'id': 'residents-18-65', 'path': ['district_detail', '2021', 'age_groups', 'age_18_to_under_65'] }, + { 'id': 'residents-65-above', 'path': ['district_detail', '2021', 'age_groups', 'age_65_and_above'] } + ] + + let sum = 0 + items.forEach((item) => { + sum += this.model.districtData.valueByPath(item.path) + }) + + let barOffset = 0 + items.forEach((item) => { + const c = this.componentById(item.id) + const d = this.model.districtData.valueByPath(item.path) + const percentage = d / sum * 100 + c.setProperties( + { + 'value': this.formatNumber(d), + 'percentage': this.formatNumber(percentage), + barOffset + }) + barOffset += percentage + }) + } + + updateSectionBirths() { + const births = this.model.districtData.valueByPath(['district_detail', '2021', 'births']) + const birthsTotal = this.model.birthsTotal() + const percent = births / birthsTotal * 100 + + this.setProperties('births', { 'value': this.formatNumber(births) }) + this.setProperties('births-percent', { 'value': this.formatNumber(percent) }) + this.setProperties('births-total', { 'value': this.formatNumber(birthsTotal) }) + this.setProperties('births-rate', { 'value': this.formatNumber(0) }) // TODO: Use the correct value from data + + + const c = this.componentById('births-chart') + c.setProperties({ 'values': this.model.birthsInDistrictsArray() }) + } + onDataChanged(data) { this.model.setDataObject(data) - this.model.setDistrictObject(this.model.districtId) + this.model.setDistrictData(this.model.districtId) + + this.model.districtCount = data.length - const d = { 'data': this.model.data, 'districtId': this.model.districtId } + const d = { 'data': this.model.data.data, 'districtId': this.model.districtId } const c = this.componentById('district-select') c.setWithData(d) c.bindDistrictChanged(this.onDistrictChanged) - this.renderAgeSection() + this.updateView() } onDistrictChanged(id) { this.model.setDistrictId(id + 1) - this.model.setDistrictObject(id + 1) - - this.renderAgeSection() - } + this.model.setDistrictData(id + 1) - /** - * Generates a random RGB color. - * - * @returns {string} A random RGB color string. - */ - static randomColor() { - const red = Math.floor(Math.random() * 256) - const green = Math.floor(Math.random() * 256) - const blue = Math.floor(Math.random() * 256) - return `rgb(${red}, ${green}, ${blue})` + this.updateView() } } \ No newline at end of file diff --git a/src/appModel.js b/src/appModel.js index e3fff83..dffffb7 100644 --- a/src/appModel.js +++ b/src/appModel.js @@ -1,12 +1,13 @@ -import { Model } from '@sndcds/mvc' +import { Model, DataObject } from '@sndcds/mvc' export default class AppModel extends Model { constructor() { super() this.data = null + this.districtCount = 0 this.districtNames = null - this.districtObject = null + this.districtData = null this.districtId = null } @@ -15,7 +16,8 @@ export default class AppModel extends Model { } setDataObject(data) { - this.data = data + this.data = new DataObject(data) + this.districtCount = data.length this.districtNames = data.map((item) => item.district_name) this.setStorage('data', data) } @@ -25,13 +27,64 @@ export default class AppModel extends Model { this.setStorage('districtId', districtId) } - setDistrictObject(districtId) { + setDistrictData(districtId) { const condition = (district) => district.district_id === districtId const items = this.data.filter(condition) if (items.length > 0) { - this.districtObject = items[0] - this.setStorage('districtObject', items[0]) + this.districtData = new DataObject(items[0]) + this.setStorage('districtData', items[0]) } } + + /** + * Get district name. + */ + districtName(districtId) { + if (districtId === undefined) { + districtId = this.districtId + } + return this.districtNames[districtId - 1] + } + + /** + * Get number of residents total for a specific year. + */ + residentsTotal(year) { + // TODO: Use year + // TODO: Check! Get value from data? + const values = this.residentsInDistrictsArray() + let sum = 0 + + values.forEach((v) => { + sum += v + }) + + return sum + } + + residentsInDistrictsArray(year) { + // TODO: Use year + // TODO: Check! + return this.data.data.map((item) => item.district_detail['2021'].residents) + } + + birthsTotal(year) { + // TODO: Use year + // TODO: Check! Get value from data? + const values = this.birthsInDistrictsArray() + let sum = 0 + + values.forEach((v) => { + sum += v + }) + + return sum + } + + birthsInDistrictsArray(year) { + // TODO: Use year + // TODO: Check! + return this.data.data.map((item) => item.district_detail['2021'].births) + } } \ No newline at end of file diff --git a/src/components/button.js b/src/components/button.js index dd535ed..ee8d181 100644 --- a/src/components/button.js +++ b/src/components/button.js @@ -1,6 +1,5 @@ import { Component } from '@sndcds/mvc' - export default class Button extends Component { constructor(parent, id, setupData) { super(parent, id, setupData) diff --git a/src/components/demoComponent.js b/src/components/demoComponent.js index 4d8ab93..3ab5e51 100644 --- a/src/components/demoComponent.js +++ b/src/components/demoComponent.js @@ -4,7 +4,10 @@ import { Component } from '@sndcds/mvc' export default class DemoComponent extends Component { constructor(parent, id, setupData) { super(parent, id, setupData) - this.demoProperty = 0 + this.values = null + this.maxValue = 'auto' + this.width = 100 + this.height = 100 this.setProperties(setupData) } @@ -14,26 +17,109 @@ export default class DemoComponent extends Component { } propertyNames() { - return super.propertyNames(['demoProperty']) + return super.propertyNames(['values', 'maxValue', 'width', 'height']) } - setMessage(message) { - if (this.e) { - if (this.childs.length < 1) { - this.e.innerHTML = `

${this.id}

` - } - // this.e.style.backgroundColor = '#88a'; + propertiesChanged() { + if (this.e !== null) { + this.e.replaceChildren() + this.buildSvgContent() } - - this.childs.forEach((item) => { - item.setMessage(message) - }) } + build() { - this.e = this.addDomElement('p') - this.e.innerText = 'Hello' - this.e.style.backgroundColor = '#999' + const svg = this.e = document.createElementNS('http://www.w3.org/2000/svg', 'svg') + svg.setAttribute('width', this.width) + svg.setAttribute('height', this.height) + svg.setAttribute('viewBox', `0 0 ${this.width} ${this.height}`) + + this.domAddClasses(svg, this.classList) + this.parent.e.appendChild(svg) + + this.buildSvgContent() + + // polygon.setAttribute('points', '20,4 50,1 47,50 0,30') + this.buildChilds() } + + buildSvgContent() { + if (this.values !== null) { + if (Array.isArray(this.values)) { + const n = this.values.length + const gap = 4 + const w = (this.width - gap * (n - 1)) / n + const svg = this.e + + let maxValue = 100 + if (this.maxValue === 'auto') { + // TODO: Check this..., Math.max() has some limitations! + maxValue = Math.max.apply(null, this.values) + } + else { + maxValue = this.maxValue + } + + const barsCount = this.values.length + let sum = 0 + for (let i = 0; i < barsCount; i++) { + const polygon = document.createElementNS('http://www.w3.org/2000/svg', 'polygon') + svg.appendChild(polygon) + + const h = this.values[i] / maxValue * this.height + const x = (w + gap) * i + const y = this.height - h + + const x1 = x.toLocaleString('en-US', { style: 'decimal', minimumFractionDigits: 0, maximumFractionDigits: 3 }) + const x2 = (x + w).toLocaleString('en-US', { style: 'decimal', minimumFractionDigits: 0, maximumFractionDigits: 3 }) + const y1 = y.toLocaleString('en-US', { style: 'decimal', minimumFractionDigits: 0, maximumFractionDigits: 3 }) + const y2 = (y + h).toLocaleString('en-US', { style: 'decimal', minimumFractionDigits: 0, maximumFractionDigits: 3 }) + + polygon.setAttribute('class', 'xxx') + polygon.setAttribute('points', `${x1},${y1} ${x2},${y1} ${x2},${y2} ${x1},${y2}`) + polygon.setAttribute('fill', '#0069f6') + polygon.setAttribute('stroke', 'none') + + svg.appendChild(polygon) + + sum += this.values[i] + } + + // Mean line + { + const polygon = document.createElementNS('http://www.w3.org/2000/svg', 'polygon') + svg.appendChild(polygon) + + const mean = sum / barsCount + const h = 1 + const x = 0 + const y = sum / barsCount / maxValue * this.height + + const x1 = 0 + const x2 = this.floatToString(this.width) + const y1 = this.floatToString(y) + const y2 = this.floatToString(y + 0.5) + + polygon.setAttribute('points', `${x1},${y1} ${x2},${y1} ${x2},${y2} ${x1},${y2}`) + polygon.setAttribute('fill', '#ccc') + polygon.setAttribute('stroke', 'none') + polygon.style.mixBlendMode = 'multiply' // Set the blend mode + + svg.appendChild(polygon) + } + } + } + } + + /** + * Converts a float to a string in a compact form without trailing zeroes and a defined number ogf decimals. + */ + floatToString(value, decimals) { + // TODO: Check! Is there a better/faster method? + if (decimals === undefined) { + decimals = 2 + } + return value.toLocaleString('en-US', { style: 'decimal', minimumFractionDigits: 0, maximumFractionDigits: decimals }) + } } \ No newline at end of file diff --git a/src/components/numView.js b/src/components/numView.js new file mode 100644 index 0000000..4374f1f --- /dev/null +++ b/src/components/numView.js @@ -0,0 +1,88 @@ +import { Component } from '@sndcds/mvc' + + +/** + * Component for displaying a label, value, bar and percentage. + * Can be used for displaying numeric information, i. e. of statistics and other number related contexts. + * + * @since 20.9.2023 + */ +export default class NumView extends Component { + /** + * Create a NumView. + * + * @param {Component} parent - The parent component. + * @param {String} id - The id of the new Component. + * @param {any} setupData - Optional set up data. + */ + constructor(parent, id, setupData) { + super(parent, id, setupData) + this.label = 'Label' + this.value = 0 + this.preText = null // Text before the value, i. e. a currency. + this.postText = null // Text after the value, i. e. a unit. + + this.setProperties(setupData) + } + + defaultClass() { + return 'custom-num-percent' + } + + /** + * Build the DOM Elements for HTML rendering og the component. + * + * This method mus be overridden in derived classes. + * + * Styling: + * One solution is to assign classes to the emelemts. + */ + build() { + this.e = this.addDomElement('div') + + let a = this.e.appendChild(this.domCreateElement('div')) + a.style.textAlign = 'center' + a.innerText = this.label + a.className = this.prefixedClassName('label') + + a = this.e.appendChild(this.domCreateElement('div')) + a.style.textAlign = 'center' + a.innerText = this.value + a.className = this.prefixedClassName('value') + + this.buildChilds() + } + + propertyNames() { + const names = [ + 'label', + 'value', + 'preText', + 'postText' + ] + + return super.propertyNames(names) + } + + propertiesChanged() { + if (this.e !== null) { + let text = this.value + if (this.postText !== null) { + text += this.postText + } + this.e.children.item(1).innerText = text + } + } + + + gradient() { + const p = parseFloat(this.percentage) + const color1 = this.barColor1 + const color2 = this.barColor2 + const spot1 = `${this.barOffset}%` + const spot2 = `${this.barOffset + 0.1}%` + const spot3 = `${this.barOffset + p - 0.1}%` + const spot4 = `${this.barOffset + p}%` + return `linear-gradient(90deg, ${color1} 0%, ${color1} ${spot1}, ${color2} ${spot2}, ${color2} ${spot3}, ${color1} ${spot4})` + } +} \ No newline at end of file diff --git a/src/components/text.js b/src/components/text.js new file mode 100644 index 0000000..c7ad583 --- /dev/null +++ b/src/components/text.js @@ -0,0 +1,41 @@ +import { Component } from '@sndcds/mvc' + + +export default class Text extends Component { + constructor(parent, id, setupData) { + super(parent, id, setupData) + + this.html = null + + this.setProperties(setupData) + } + + defaultClass() { + return 'custom-text' + } + + propertyNames() { + const names = ['html'] + return super.propertyNames(names) + } + + propertiesChanged() { + if (this.e !== null) { + this.e.innerHTML = this.html + } + } + + build() { + this.e = this.addDomElement('text') + this.e.innerHTML = this.html + + this.buildChilds() + } + + addText(type, text) { + const types = ['h1', 'h2', 'h3', 'h4', 'p'] + if (types.includes(type)) { + this.html = `<${type}>text` + } + } +} \ No newline at end of file diff --git a/src/components/vBarsView.js b/src/components/vBarsView.js deleted file mode 100644 index 3381d3b..0000000 --- a/src/components/vBarsView.js +++ /dev/null @@ -1,41 +0,0 @@ -import { Component } from '@sndcds/mvc' - - -/** - * Component for displaying a label, value, bar and percentage. - * Can be used for displaying numeric information, i. e. of statistics and other number related contexts. - * - * @since 20.9.2023 - */ -export default class VBarsView extends Component { - constructor(parent, id, setupData) { - super(parent, id, setupData) - this.heights = [10, 20, 100, 30, 2, 10, 60, 50, 10, 39, 54, 12, 30] - this.setProperties(setupData) - } - - defaultClass() { - return 'custom-vbars' - } - - propertyNames() { - return super.propertyNames() - } - - build() { - this.e = this.addDomElement('div') - this.e.style.display = 'grid' - this.e.style.gridTemplateColumns = 'repeat(13, 1fr)' - - this.heights.forEach((height) => { - height = Math.floor(Math.random() * 90 + 10) - const a = this.e.appendChild(this.domCreateElement('div')) - a.style.height = `${height}%` - a.style.position = 'relative' - a.style.top = `${100 - height}%` - }) - - - this.buildChilds() - } -} \ No newline at end of file diff --git a/src/index.html b/src/index.html index 2b3b23a..06bda5a 100644 --- a/src/index.html +++ b/src/index.html @@ -1,6 +1,6 @@ - + Sozialatlas Dashboard - Stadt Flensburg @@ -8,15 +8,16 @@ - - + + - +

- + + diff --git a/src/index.js b/src/index.js index 8e5ef40..06a4e29 100644 --- a/src/index.js +++ b/src/index.js @@ -1,19 +1,22 @@ import App from './app.js' import AppModel from './appModel.js' import Grid from './components/grid.js' +import Text from './components/text.js' import DemoComponent from './components/demoComponent.js' import Button from './components/button.js' import DistrictSelect from './components/districtSelect.js' +import NumView from './components/numView.js' import NumPercentView from './components/numPercentView.js' -import VBarsView from './components/vBarsView.js' + export { App, AppModel, Grid, + Text, DemoComponent, Button, DistrictSelect, - NumPercentView, - VBarsView + NumView, + NumPercentView } \ No newline at end of file diff --git a/src/main.css b/src/main.css index 1ec7680..036cf49 100644 --- a/src/main.css +++ b/src/main.css @@ -2,7 +2,7 @@ html, body { font-family: sans-serif; margin: 0; - padding: 0; + padding: 12px; color: #333; } @@ -10,17 +10,47 @@ body { --primary-color: #0069f6; --primary-light-color: #d1e4fd; --background-color: #fff; + --separator-color: #ccc; } button { + font-size: 1em; color: var(--primary-color); background-color: var(--background-color); border: 1px solid var(--primary-color); + padding: 8px 20px; } button:hover { color: var(--background-color); background-color: var(--primary-color); + transition: 0.2s; +} + +h1 { + font-weight: 300; + border-bottom: 1px solid var(--separator-color); +} + +.xxx { + transition: 0.1s; +} + +.xxx:hover { + fill: #d1e4fd; +} + +.supertext { + background-color: yellow; + height: 30px; +} + +.custom-demo-component { + grid-column: span 2; + width: 100%; + height: 100%; + max-height: 200px; + padding: 0; } .grid { @@ -64,8 +94,8 @@ button:hover { .num-percent .bar, .custom-num-percent .bar { - height: 10px; - border-radius: 2px; + height: 14px; + border-radius: 7px; } .custom-num-percent .bar { diff --git a/src/main.js b/src/main.js index 341e4d0..189016f 100644 --- a/src/main.js +++ b/src/main.js @@ -1,105 +1,131 @@ -import { - Model, - View, - Controller, - Component -} from '@sndcds/mvc' +import { Model, View, Controller, Component } from '@sndcds/mvc' import { App, AppModel, Grid, + Text, DemoComponent, Button, DistrictSelect, - NumPercentView, - VBarsView + NumView, + NumPercentView } from './index.js' -// Sozialatlas - // Create model const model = new AppModel() // Create view const view = new View() -// Create Components -const section1 = new Grid(view, 'section-1', { 'classList': 'section apple padding-10' }) -new Button(section1, 'button-1', { 'onClick': button1OnClick }) -new Button(section1, 'button-2', { 'label': 'District 1', 'tag': 1, 'onClick': button2OnClick }) -new Button(section1, 'button-3', { 'label': 'District 2', 'tag': 2, 'onClick': button2OnClick }) -new DistrictSelect(section1, 'district-select') - -const section2 = new Grid(view, 'section-2', { 'classList': 'section banana padding-10' }) -const subView2_1 = new Grid(section2, 'subview-2-1', { 'classList': 'section ibm' }) -const subView2_1_1 = new Grid(subView2_1, 'subview-2-1-1', { 'classList': 'section lenovo' }) -for (let i = 1; i <= 3; i++) { - const labels = ['Alpha', 'Beta', 'Gamma'] - new NumPercentView(subView2_1_1, `value-display-${i}`, { 'label': labels[i - 1], 'value': 123, 'percentage': 87.3, 'barOffset': 13.2, 'barColor1': '#d1e4fd', 'barColor2': '#0069f6' }) -} +// Create app +const app = new App(model, view) +app.configurate({ 'locale': 'de-DE' }) + +// Create views and components +{ + const section = new Grid(view, 'section-1', { 'classList': 'section apple padding-10' }) + new Button(section, 'button-1', { 'label': 'Hide', 'onClick': button1OnClick }) + new Button(section, 'button-2', { 'label': 'Show', 'onClick': button2OnClick }) + new DistrictSelect(section, 'district-select') -const subView2_1_2 = new Grid(subView2_1, 'subview-2-1', { 'classList': 'section lenovo' }) -for (let i = 1; i <= 8; i++) { - const labels = ['0 - 18', '18 - 30', '30 - 45', '45 - 65', '65 - 80', '80+', '0 - 8', '60+'] - new NumPercentView(subView2_1_2, `age-view-${i}`, { 'classList': 'num-percent', 'label': labels[i - 1], 'value': 123, 'percentage': 87.3, 'barOffset': 13.2, 'barColor1': '#d1e4fd', 'barColor2': '#0069f6' }) + const fastButtonSection = new Grid(view, 'fast-button-section', { 'classList': 'section arena padding-10' }) + for (let i = 1; i <= 13; i++) { + new Button(fastButtonSection, `district-button-${i}`, { 'classList': '', 'label': i, 'tag': i - 1, 'onClick': button2OnClick }) + } + + new Text(view, 'text-distict-details', { 'classList': 'supertext', 'html': '

District' }) } -const subView2_2 = new Grid(section2, 'subview-2-2', { 'classList': 'section sgi' }) -new VBarsView(subView2_2, 'vbar-1', { 'classList': 'custom-vbar' }) -const section3 = new Grid(view, 'section-3', { 'classList': 'section arena padding-10' }) -for (let i = 1; i <= 13; i++) { - new Button(section3, `district-button-${i}`, { 'classList': '', 'label': i, 'tag': i - 1, 'onClick': button2OnClick }) +// Section Einwohner*innen im Stadtteil +{ + new Text(view, 'text-1', { 'classList': 'supertext', 'html': '

Einwohner*innen im Stadtteil' }) + + const section = new Grid(view, 'section-3', { 'classList': 'section banana padding-10' }) + + const subView1 = new Grid(section, 'subview-2-1', { 'classList': 'section ibm' }) + + const subView1_1 = new Grid(subView1, 'subview-2-1-1', { 'classList': 'section lenovo' }) + new NumView(subView1_1, 'residents-in-destrict', { 'label': 'Anzahl' }) + new NumView(subView1_1, 'residents-percent-in-destrict', { 'label': 'Anteil Stadt', 'postText': ' %' }) + new NumView(subView1_1, 'residents-total', { 'label': 'Stadt gesamt' }) + + const subView1_2 = new Grid(subView1, 'subview-2-1', { 'classList': 'section lenovo' }) + for (let i = 1; i <= 8; i++) { + const labels = ['0 - 18', '18 - 30', '30 - 45', '45 - 65', '65 - 80', '80+', '0 - 8', '60+'] + new NumPercentView(subView1_2, `age-view-${i}`, { 'classList': 'num-percent', 'label': labels[i - 1], 'barColor1': '#d1e4fd', 'barColor2': '#0069f6' }) + } + + const subView2 = new Grid(section, 'subview-2-2', { 'classList': 'section sgi' }) + + new DemoComponent(subView2, 'svg', { 'classList': 'custom-demo-component', 'width': 200, 'height': 100, 'maxValue': 'auto', 'values': [10, 20, 30, 40, 50, 60, 70, 80, 90, 100] }) } +// Section Schule/Arbeit/Rente +{ + new Text(view, 'text-3', { 'classList': 'supertext', 'html': '

Schule/Arbeit/Rente' }) -/* -for (let i = 4; i <= 2; i++) { - const labels = ['SUN', 'DEC', 'Apple', 'NeXT', 'HP', 'Compaq', 'Lenovo', 'sgi', 'Atari', 'Commodore', 'IBM'] - const randomIndex = Math.floor(Math.random() * labels.length) - const label = labels[randomIndex] + const section = new Grid(view, 'section-4', { 'classList': 'section banana padding-10' }) - const percentage = Math.floor(Math.random() * 80) - const barOffset = Math.floor(Math.random() * (100 - percentage)) - const setupData = { - label, - value: Math.floor(Math.random() * 1000), - percentage, - barOffset, - barColor1: App.randomColor(), - barColor2: App.randomColor() - } + const subView1 = new Grid(section, 'subview-2-1', { 'classList': 'section lenovo' }) + new NumPercentView(subView1, 'residents-0-18', { 'classList': 'num-percent', 'label': '0 - 18', 'barColor1': '#d1e4fd', 'barColor2': '#0069f6' }) + new NumPercentView(subView1, 'residents-18-65', { 'classList': 'num-percent', 'label': '18 - 65', 'barColor1': '#d1e4fd', 'barColor2': '#0069f6' }) + new NumPercentView(subView1, 'residents-65-above', { 'classList': 'num-percent', 'label': '65+', 'barColor1': '#d1e4fd', 'barColor2': '#0069f6' }) - new NumPercentView(container2, `pop-${i}`, setupData) + const subView2 = new Grid(section, 'subview-2-1', { 'classList': 'section sgi' }) + new DemoComponent(subView2, 'svg2', { 'classList': 'custom-demo-component', 'width': 200, 'height': 100, 'values': [10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 10, 1000, 30] }) } -*/ -// Create app -const app = new App(model, view) -app.configurate({ 'locale': 'de-DE' }) + +// Section Geburten +{ + new Text(view, 'text-4', { 'classList': 'supertext', 'html': '

Geburten' }) + + const section = new Grid(view, 'section-5', { 'classList': 'section banana padding-10' }) + + const subView1 = new Grid(section, 'subview-2-1', { 'classList': 'section lenovo' }) + new NumView(subView1, 'births', { 'classList': 'num-percent', 'label': 'Anzahl' }) + new NumView(subView1, 'births-percent', { 'classList': 'num-percent', 'label': 'Anteil Stadt', 'postText': ' %' }) + new NumView(subView1, 'births-total', { 'classList': 'num-percent', 'label': 'Stadt gesamt' }) + new NumView(subView1, 'births-rate', { 'classList': 'num-percent', 'label': 'Quote' }) + + const subView2 = new Grid(section, 'subview-2-2', { 'classList': 'section sgi' }) + new DemoComponent(subView2, 'births-chart', { 'classList': 'custom-demo-component', 'width': 200, 'height': 100, 'maxValue': 'auto', 'values': [10, 20, 30, 40, 50, 60, 70, 80, 90, 100] }) +} + + + + + + + + app.buildView('root') // Init app, must be called after configurate and buildView -app.initApp('./details.json', 13) +app.initApp('./../static/details.json', 13) // Handlers function button1OnClick(component) { - component.e.innerText = 'clicked!' - const c = app.componentById('value-display-1') - c.setProperties({ 'value': 987, 'barColor1': 'red', 'barColor2': 'yellow' }) - const s = app.componentById('section-2') - s.e.style.display = 'none' -// s.e.style.visibility = 'hidden' + for (let i = 2; i <= 5; i++) { + const s = app.componentById(`section-${i}`) + if (s !== undefined) { + s.e.style.display = 'none' + } + } } function button2OnClick(component) { - app.onDistrictChanged(component.tag) - const n = app.view.countDescendants() - const s = app.componentById('section-2') - s.e.style.display = 'grid' -// s.e.style.visibility = 'visible' + console.log(app.view.countDescendants()) + + for (let i = 2; i <= 5; i++) { + const s = app.componentById(`section-${i}`) + if (s !== undefined) { + s.e.style.display = 'grid' + } + } } \ No newline at end of file From 2972c4be631fbda77f34ff76c18e0cad788cfffd Mon Sep 17 00:00:00 2001 From: Roald Christesen Date: Wed, 4 Oct 2023 14:56:24 +0200 Subject: [PATCH 2/8] changed version of @sndcds/mvc to use --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e99b864..ac38130 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "staticPath": "static" }, "dependencies": { - "@sndcds/mvc": "^0.0.31" + "@sndcds/mvc": "^0.0.33" }, "devDependencies": { "@parcel/config-default": "^2.9.3", From befbc6981368d209816d0a479890894a6d823019 Mon Sep 17 00:00:00 2001 From: Aurelius Wendelken Date: Wed, 4 Oct 2023 16:03:13 +0200 Subject: [PATCH 3/8] fix failing build, updated pnpm lock file --- pnpm-lock.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 58425ad..d7fea7b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -6,8 +6,8 @@ settings: dependencies: '@sndcds/mvc': - specifier: ^0.0.31 - version: 0.0.31 + specifier: ^0.0.33 + version: 0.0.33 devDependencies: '@parcel/config-default': @@ -1141,8 +1141,8 @@ packages: nullthrows: 1.1.1 dev: true - /@sndcds/mvc@0.0.31: - resolution: {integrity: sha512-m5XM+8ECMgGYlwLuTemELDHWs9t0o0XF+CQEbcQGTrzrwudewm0fz/OQxRd2gAbQdH7UHczYerQv0mHh3wHhuw==} + /@sndcds/mvc@0.0.33: + resolution: {integrity: sha512-8Ef8txzpmxa7CDr+OXKukPF7Kaj4nrSdM+2Ewuqzs9Z1YU4GYkVlUYwoq4GiDcXSYrPNSnhOX9kQsrVMixGTgQ==} dev: false /@swc/core-darwin-arm64@1.3.91: From e5e02fc0f69f66005061f2cd41e5cd8a3d6a1efa Mon Sep 17 00:00:00 2001 From: Aurelius Wendelken Date: Wed, 4 Oct 2023 16:15:34 +0200 Subject: [PATCH 4/8] removed javascript file from project root --- index.js | 13 ------------- 1 file changed, 13 deletions(-) delete mode 100644 index.js diff --git a/index.js b/index.js deleted file mode 100644 index 96feca2..0000000 --- a/index.js +++ /dev/null @@ -1,13 +0,0 @@ -import Model from './lib/model.js' -import View from './lib/view.js' -import Router from './lib/router.js' -import Controller from './lib/controller.js' -import Component from './lib/component.js' - -export { - Model, - View, - Router, - Controller, - Component -} \ No newline at end of file From bdc465ef258bdfc282cb788bcdf18ffe0787030b Mon Sep 17 00:00:00 2001 From: Aurelius Wendelken Date: Wed, 4 Oct 2023 16:33:02 +0200 Subject: [PATCH 5/8] add npm script preinstall task --- package.json | 1 + scripts/pre-push | 26 ++++++++++++++++++++++++++ 2 files changed, 27 insertions(+) create mode 100644 scripts/pre-push diff --git a/package.json b/package.json index ac38130..f76b380 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "build": "parcel build --public-url '.' --no-cache", "htmlhint": "htmlhint \"**/*.html\" --ignore \"dist/**/*.html\" --format unix", "prettier": "npm:@yuhr/prettier", + "preinstall": "cp scripts/* .git/hooks", "lint:js": "pnpm exec eslint src", "lint:html": "pnpm run htmlhint", "lint:css": "pnpm exec stylelint \"**/*.css\" && pnpm exec prettier --check \"**/*.css\"", diff --git a/scripts/pre-push b/scripts/pre-push new file mode 100644 index 0000000..34a0638 --- /dev/null +++ b/scripts/pre-push @@ -0,0 +1,26 @@ +#!/bin/bash + +CMD="pnpm run install && pnpm run build" # Command that runs your tests +protected_branch='main' + +# Check if we actually have commits to push +commits=`git log @{u}..` + +if [ -z "$commits" ]; then + exit 0 +fi + +current_branch=$(git symbolic-ref HEAD | sed -e 's,.*/\(.*\),\1,') + +if [[ $current_branch = $protected_branch ]]; then + $CMD + + RESULT=$? + + if [ $RESULT -ne 0 ]; then + echo "failed $CMD" + exit 1 + fi +fi + +exit 0 \ No newline at end of file From d641844200f21c20f9330f49d62e75f7240f78f2 Mon Sep 17 00:00:00 2001 From: Aurelius Wendelken Date: Wed, 4 Oct 2023 17:01:14 +0200 Subject: [PATCH 6/8] updated data source and fixed mappings --- src/app.js | 3 --- src/appModel.js | 8 ++++---- src/components/districtSelect.js | 2 +- src/main.css | 2 +- src/main.js | 4 +--- static/details.json | 2 +- 6 files changed, 8 insertions(+), 13 deletions(-) diff --git a/src/app.js b/src/app.js index ae0a7f3..776b5d5 100644 --- a/src/app.js +++ b/src/app.js @@ -2,7 +2,6 @@ import { Controller, Component } from '@sndcds/mvc' export default class App extends Controller { - /* eslint no-useless-constructor: 0 */ constructor(model, view) { super(model, view) @@ -29,8 +28,6 @@ export default class App extends Controller { } updateView() { - console.log(`

${this.model.districtName()}

`) - this.setProperties('text-distict-details', { 'html': `

Stadtteil: ${this.model.districtName()}

` }) const residentsInDestrict = this.model.districtData.valueByPath(['district_detail', '2021', 'residents']) diff --git a/src/appModel.js b/src/appModel.js index dffffb7..851bb0c 100644 --- a/src/appModel.js +++ b/src/appModel.js @@ -18,7 +18,7 @@ export default class AppModel extends Model { setDataObject(data) { this.data = new DataObject(data) this.districtCount = data.length - this.districtNames = data.map((item) => item.district_name) + this.districtNames = data.detail.map((item) => item.district_name) this.setStorage('data', data) } @@ -29,7 +29,7 @@ export default class AppModel extends Model { setDistrictData(districtId) { const condition = (district) => district.district_id === districtId - const items = this.data.filter(condition) + const items = this.data.data.detail.filter(condition) if (items.length > 0) { this.districtData = new DataObject(items[0]) @@ -66,7 +66,7 @@ export default class AppModel extends Model { residentsInDistrictsArray(year) { // TODO: Use year // TODO: Check! - return this.data.data.map((item) => item.district_detail['2021'].residents) + return this.data.data.detail.map((item) => item.residents) } birthsTotal(year) { @@ -85,6 +85,6 @@ export default class AppModel extends Model { birthsInDistrictsArray(year) { // TODO: Use year // TODO: Check! - return this.data.data.map((item) => item.district_detail['2021'].births) + return this.data.data.detail.map((item) => item.births) } } \ No newline at end of file diff --git a/src/components/districtSelect.js b/src/components/districtSelect.js index 809f7dc..535c9d2 100644 --- a/src/components/districtSelect.js +++ b/src/components/districtSelect.js @@ -22,7 +22,7 @@ export default class DistrictSelect extends Component { setWithData(data) { const selectElement = this.domCreateElement('select') - data.data.forEach((item) => { + data.data.detail.forEach((item) => { const optionElement = this.domCreateElement('option') optionElement.value = item.district_id diff --git a/src/main.css b/src/main.css index 036cf49..8bf7cd7 100644 --- a/src/main.css +++ b/src/main.css @@ -32,7 +32,7 @@ h1 { border-bottom: 1px solid var(--separator-color); } -.xxx { +.xxx { transition: 0.1s; } diff --git a/src/main.js b/src/main.js index 189016f..3c6041c 100644 --- a/src/main.js +++ b/src/main.js @@ -105,7 +105,7 @@ app.configurate({ 'locale': 'de-DE' }) app.buildView('root') // Init app, must be called after configurate and buildView -app.initApp('./../static/details.json', 13) +app.initApp('./details.json', 13) // Handlers @@ -120,8 +120,6 @@ function button1OnClick(component) { function button2OnClick(component) { - console.log(app.view.countDescendants()) - for (let i = 2; i <= 5; i++) { const s = app.componentById(`section-${i}`) if (s !== undefined) { diff --git a/static/details.json b/static/details.json index 2240466..b009b67 100644 --- a/static/details.json +++ b/static/details.json @@ -1 +1 @@ -[{"district_id":1,"district_name":"Altstadt","district_detail":{"2021":{"residents":3866,"births":31,"age_ratio":13.7,"age_groups":{"age_18_to_under_30":1338,"age_30_to_under_45":951,"age_45_to_under_65":804,"age_65_to_under_80":265,"age_0_to_under_7":174,"age_60_and_above":565,"age_80_and_above":148,"age_to_under_18":360,"age_18_to_under_65":3093,"age_65_and_above":413},"employed_residents":1593,"unemployed_residents":226,"unemployment_characteristics":{"percentage_sgb_iii":31.0,"percentage_sgb_ii":69.0,"percentage_foreign_citizenship":23.0,"percentage_female":41.2,"percentage_age_under_25":11.9},"housing_benefit":116,"housing_assistance":{"notices_of_rent_arrears":null,"termination_rent_arrears":null,"termination_for_conduct":null,"action_for_eviction":null,"eviction_notice":5,"eviction_carried":5},"risk_of_homelessness":null,"benefits_age_15_to_under_65":{"employable_with_benefits":377,"unemployment_benefits":70,"basic_income":55,"assisting_benefits":15},"benefits_characteristics":{"beneficiaries_sgbii":475,"unemployability":98,"employability":377,"percentage_females":40.3,"percentage_single_parents":7.2,"percentage_foreign_citizenship":31.3},"inactive_beneficiaries_in_households":98,"basic_benefits_income":{"male":245,"female":158,"age_18_to_under_65":55,"age_65_and_above":55},"migration_background":{"foreign_citizenship":999,"german_citizenship":375}}}},{"district_id":2,"district_name":"Neustadt","district_detail":{"2021":{"residents":4850,"births":61,"age_ratio":10.3,"age_groups":{"age_18_to_under_30":1597,"age_30_to_under_45":1162,"age_45_to_under_65":977,"age_65_to_under_80":277,"age_0_to_under_7":347,"age_60_and_above":551,"age_80_and_above":92,"age_to_under_18":745,"age_18_to_under_65":3736,"age_65_and_above":369},"employed_residents":1768,"unemployed_residents":383,"unemployment_characteristics":{"percentage_sgb_iii":20.1,"percentage_sgb_ii":79.9,"percentage_foreign_citizenship":30.3,"percentage_female":34.7,"percentage_age_under_25":12.0},"housing_benefit":172,"housing_assistance":{"notices_of_rent_arrears":null,"termination_rent_arrears":4,"termination_for_conduct":null,"action_for_eviction":5,"eviction_notice":9,"eviction_carried":5},"risk_of_homelessness":5,"benefits_age_15_to_under_65":{"employable_with_benefits":809,"unemployment_benefits":77,"basic_income":122,"assisting_benefits":26},"benefits_characteristics":{"beneficiaries_sgbii":1116,"unemployability":307,"employability":809,"percentage_females":41.9,"percentage_single_parents":7.5,"percentage_foreign_citizenship":40.5},"inactive_beneficiaries_in_households":307,"basic_benefits_income":{"male":61,"female":49,"age_18_to_under_65":122,"age_65_and_above":76},"migration_background":{"foreign_citizenship":1748,"german_citizenship":443}}}},{"district_id":3,"district_name":"Nordstadt","district_detail":{"2021":{"residents":12525,"births":124,"age_ratio":23.8,"age_groups":{"age_18_to_under_30":2349,"age_30_to_under_45":2722,"age_45_to_under_65":3348,"age_65_to_under_80":1494,"age_0_to_under_7":946,"age_60_and_above":2642,"age_80_and_above":448,"age_to_under_18":2164,"age_18_to_under_65":8419,"age_65_and_above":1942},"employed_residents":4164,"unemployed_residents":823,"unemployment_characteristics":{"percentage_sgb_iii":21.7,"percentage_sgb_ii":78.3,"percentage_foreign_citizenship":34.9,"percentage_female":42.5,"percentage_age_under_25":8.9},"housing_benefit":523,"housing_assistance":{"notices_of_rent_arrears":null,"termination_rent_arrears":19,"termination_for_conduct":4,"action_for_eviction":15,"eviction_notice":20,"eviction_carried":10},"risk_of_homelessness":15,"benefits_age_15_to_under_65":{"employable_with_benefits":2,"unemployment_benefits":179,"basic_income":283,"assisting_benefits":62},"benefits_characteristics":{"beneficiaries_sgbii":2170,"unemployability":585,"employability":1585,"percentage_females":48,"percentage_single_parents":14.5,"percentage_foreign_citizenship":37.5},"inactive_beneficiaries_in_households":585,"basic_benefits_income":{"male":112,"female":86,"age_18_to_under_65":283,"age_65_and_above":248},"migration_background":{"foreign_citizenship":4109,"german_citizenship":1673}}}},{"district_id":4,"district_name":"Westliche Höhe","district_detail":{"2021":{"residents":8015,"births":69,"age_ratio":43.2,"age_groups":{"age_18_to_under_30":1387,"age_30_to_under_45":1485,"age_45_to_under_65":1912,"age_65_to_under_80":1276,"age_0_to_under_7":501,"age_60_and_above":2451,"age_80_and_above":714,"age_to_under_18":1241,"age_18_to_under_65":4784,"age_65_and_above":1990},"employed_residents":2451,"unemployed_residents":304,"unemployment_characteristics":{"percentage_sgb_iii":31.6,"percentage_sgb_ii":68.4,"percentage_foreign_citizenship":27.3,"percentage_female":36.8,"percentage_age_under_25":11.5},"housing_benefit":252,"housing_assistance":{"notices_of_rent_arrears":null,"termination_rent_arrears":null,"termination_for_conduct":null,"action_for_eviction":11,"eviction_notice":7,"eviction_carried":5},"risk_of_homelessness":11,"benefits_age_15_to_under_65":{"employable_with_benefits":518,"unemployment_benefits":96,"basic_income":74,"assisting_benefits":27},"benefits_characteristics":{"beneficiaries_sgbii":733,"unemployability":215,"employability":518,"percentage_females":49.4,"percentage_single_parents":12.9,"percentage_foreign_citizenship":31.9},"inactive_beneficiaries_in_households":215,"basic_benefits_income":{"male":287,"female":244,"age_18_to_under_65":74,"age_65_and_above":79},"migration_background":{"foreign_citizenship":1158,"german_citizenship":991}}}},{"district_id":5,"district_name":"Friesischer Berg","district_detail":{"2021":{"residents":6644,"births":55,"age_ratio":29.2,"age_groups":{"age_18_to_under_30":1578,"age_30_to_under_45":1376,"age_45_to_under_65":1583,"age_65_to_under_80":840,"age_0_to_under_7":337,"age_60_and_above":1681,"age_80_and_above":451,"age_to_under_18":816,"age_18_to_under_65":4537,"age_65_and_above":1291},"employed_residents":2517,"unemployed_residents":286,"unemployment_characteristics":{"percentage_sgb_iii":34.6,"percentage_sgb_ii":65.4,"percentage_foreign_citizenship":21.7,"percentage_female":40.2,"percentage_age_under_25":15.0},"housing_benefit":258,"housing_assistance":{"notices_of_rent_arrears":null,"termination_rent_arrears":4,"termination_for_conduct":null,"action_for_eviction":6,"eviction_notice":null,"eviction_carried":null},"risk_of_homelessness":6,"benefits_age_15_to_under_65":{"employable_with_benefits":514,"unemployment_benefits":99,"basic_income":88,"assisting_benefits":40},"benefits_characteristics":{"beneficiaries_sgbii":670,"unemployability":156,"employability":514,"percentage_females":52.3,"percentage_single_parents":17.9,"percentage_foreign_citizenship":23.7},"inactive_beneficiaries_in_households":156,"basic_benefits_income":{"male":74,"female":79,"age_18_to_under_65":88,"age_65_and_above":78},"migration_background":{"foreign_citizenship":907,"german_citizenship":778}}}},{"district_id":6,"district_name":"Weiche","district_detail":{"2021":{"residents":7472,"births":55,"age_ratio":34.5,"age_groups":{"age_18_to_under_30":917,"age_30_to_under_45":1433,"age_45_to_under_65":2105,"age_65_to_under_80":987,"age_0_to_under_7":549,"age_60_and_above":1958,"age_80_and_above":497,"age_to_under_18":1533,"age_18_to_under_65":4455,"age_65_and_above":1484},"employed_residents":2566,"unemployed_residents":205,"unemployment_characteristics":{"percentage_sgb_iii":44.9,"percentage_sgb_ii":55.1,"percentage_foreign_citizenship":23.4,"percentage_female":42.4,"percentage_age_under_25":12.2},"housing_benefit":163,"housing_assistance":{"notices_of_rent_arrears":null,"termination_rent_arrears":null,"termination_for_conduct":null,"action_for_eviction":null,"eviction_notice":4,"eviction_carried":null},"risk_of_homelessness":null,"benefits_age_15_to_under_65":{"employable_with_benefits":324,"unemployment_benefits":92,"basic_income":37,"assisting_benefits":9},"benefits_characteristics":{"beneficiaries_sgbii":494,"unemployability":170,"employability":324,"percentage_females":47.5,"percentage_single_parents":16,"percentage_foreign_citizenship":38.9},"inactive_beneficiaries_in_households":170,"basic_benefits_income":{"male":76,"female":90,"age_18_to_under_65":37,"age_65_and_above":45},"migration_background":{"foreign_citizenship":933,"german_citizenship":1222}}}},{"district_id":7,"district_name":"Südstadt","district_detail":{"2021":{"residents":4205,"births":51,"age_ratio":25.3,"age_groups":{"age_18_to_under_30":996,"age_30_to_under_45":954,"age_45_to_under_65":950,"age_65_to_under_80":485,"age_0_to_under_7":283,"age_60_and_above":953,"age_80_and_above":229,"age_to_under_18":591,"age_18_to_under_65":2900,"age_65_and_above":714},"employed_residents":1681,"unemployed_residents":226,"unemployment_characteristics":{"percentage_sgb_iii":26.1,"percentage_sgb_ii":73.9,"percentage_foreign_citizenship":23.9,"percentage_female":43.8,"percentage_age_under_25":10.2},"housing_benefit":204,"housing_assistance":{"notices_of_rent_arrears":null,"termination_rent_arrears":5,"termination_for_conduct":null,"action_for_eviction":6,"eviction_notice":null,"eviction_carried":null},"risk_of_homelessness":6,"benefits_age_15_to_under_65":{"employable_with_benefits":444,"unemployment_benefits":59,"basic_income":80,"assisting_benefits":17},"benefits_characteristics":{"beneficiaries_sgbii":615,"unemployability":171,"employability":444,"percentage_females":50.5,"percentage_single_parents":16.2,"percentage_foreign_citizenship":29.3},"inactive_beneficiaries_in_households":171,"basic_benefits_income":{"male":37,"female":45,"age_18_to_under_65":80,"age_65_and_above":65},"migration_background":{"foreign_citizenship":919,"german_citizenship":517}}}},{"district_id":8,"district_name":"Sandberg","district_detail":{"2021":{"residents":6702,"births":58,"age_ratio":22.1,"age_groups":{"age_18_to_under_30":2225,"age_30_to_under_45":1333,"age_45_to_under_65":1425,"age_65_to_under_80":627,"age_0_to_under_7":291,"age_60_and_above":1380,"age_80_and_above":435,"age_to_under_18":657,"age_18_to_under_65":4983,"age_65_and_above":1062},"employed_residents":2598,"unemployed_residents":327,"unemployment_characteristics":{"percentage_sgb_iii":30.6,"percentage_sgb_ii":69.4,"percentage_foreign_citizenship":19.3,"percentage_female":33.6,"percentage_age_under_25":15.0},"housing_benefit":252,"housing_assistance":{"notices_of_rent_arrears":null,"termination_rent_arrears":5,"termination_for_conduct":null,"action_for_eviction":null,"eviction_notice":8,"eviction_carried":6},"risk_of_homelessness":null,"benefits_age_15_to_under_65":{"employable_with_benefits":567,"unemployment_benefits":100,"basic_income":81,"assisting_benefits":28},"benefits_characteristics":{"beneficiaries_sgbii":706,"unemployability":139,"employability":567,"percentage_females":39.2,"percentage_single_parents":10.1,"percentage_foreign_citizenship":25.4},"inactive_beneficiaries_in_households":139,"basic_benefits_income":{"male":75,"female":70,"age_18_to_under_65":81,"age_65_and_above":74},"migration_background":{"foreign_citizenship":1117,"german_citizenship":601}}}},{"district_id":9,"district_name":"Jürgensby","district_detail":{"2021":{"residents":8371,"births":73,"age_ratio":24.9,"age_groups":{"age_18_to_under_30":2160,"age_30_to_under_45":1735,"age_45_to_under_65":2071,"age_65_to_under_80":997,"age_0_to_under_7":402,"age_60_and_above":1938,"age_80_and_above":449,"age_to_under_18":959,"age_18_to_under_65":5966,"age_65_and_above":1446},"employed_residents":3222,"unemployed_residents":427,"unemployment_characteristics":{"percentage_sgb_iii":27.2,"percentage_sgb_ii":72.8,"percentage_foreign_citizenship":21.8,"percentage_female":38.6,"percentage_age_under_25":14.3},"housing_benefit":328,"housing_assistance":{"notices_of_rent_arrears":4,"termination_rent_arrears":14,"termination_for_conduct":null,"action_for_eviction":8,"eviction_notice":4,"eviction_carried":4},"risk_of_homelessness":8,"benefits_age_15_to_under_65":{"employable_with_benefits":837,"unemployment_benefits":116,"basic_income":126,"assisting_benefits":36},"benefits_characteristics":{"beneficiaries_sgbii":1048,"unemployability":211,"employability":837,"percentage_females":43.8,"percentage_single_parents":9.3,"percentage_foreign_citizenship":26.8},"inactive_beneficiaries_in_households":211,"basic_benefits_income":{"male":77,"female":78,"age_18_to_under_65":126,"age_65_and_above":104},"migration_background":{"foreign_citizenship":1273,"german_citizenship":699}}}},{"district_id":10,"district_name":"Fruerlund","district_detail":{"2021":{"residents":6794,"births":59,"age_ratio":22.1,"age_groups":{"age_18_to_under_30":974,"age_30_to_under_45":1296,"age_45_to_under_65":1863,"age_65_to_under_80":1066,"age_0_to_under_7":425,"age_60_and_above":2113,"age_80_and_above":578,"age_to_under_18":1017,"age_18_to_under_65":4133,"age_65_and_above":1644},"employed_residents":2406,"unemployed_residents":303,"unemployment_characteristics":{"percentage_sgb_iii":30.7,"percentage_sgb_ii":69.3,"percentage_foreign_citizenship":17.5,"percentage_female":45.5,"percentage_age_under_25":6.9},"housing_benefit":370,"housing_assistance":{"notices_of_rent_arrears":null,"termination_rent_arrears":null,"termination_for_conduct":null,"action_for_eviction":null,"eviction_notice":null,"eviction_carried":null},"risk_of_homelessness":null,"benefits_age_15_to_under_65":{"employable_with_benefits":569,"unemployment_benefits":93,"basic_income":138,"assisting_benefits":28},"benefits_characteristics":{"beneficiaries_sgbii":792,"unemployability":223,"employability":569,"percentage_females":56.1,"percentage_single_parents":20.7,"percentage_foreign_citizenship":19},"inactive_beneficiaries_in_households":223,"basic_benefits_income":{"male":111,"female":119,"age_18_to_under_65":138,"age_65_and_above":110},"migration_background":{"foreign_citizenship":645,"german_citizenship":927}}}},{"district_id":11,"district_name":"Mürwik","district_detail":{"2021":{"residents":15301,"births":145,"age_ratio":52.8,"age_groups":{"age_18_to_under_30":1976,"age_30_to_under_45":2609,"age_45_to_under_65":3969,"age_65_to_under_80":2790,"age_0_to_under_7":943,"age_60_and_above":5460,"age_80_and_above":1596,"age_to_under_18":2361,"age_18_to_under_65":8554,"age_65_and_above":4386},"employed_residents":4740,"unemployed_residents":532,"unemployment_characteristics":{"percentage_sgb_iii":37.0,"percentage_sgb_ii":63.0,"percentage_foreign_citizenship":20.9,"percentage_female":48.7,"percentage_age_under_25":10.9},"housing_benefit":484,"housing_assistance":{"notices_of_rent_arrears":5,"termination_rent_arrears":6,"termination_for_conduct":null,"action_for_eviction":10,"eviction_notice":5,"eviction_carried":5},"risk_of_homelessness":10,"benefits_age_15_to_under_65":{"employable_with_benefits":988,"unemployment_benefits":197,"basic_income":137,"assisting_benefits":28},"benefits_characteristics":{"beneficiaries_sgbii":1429,"unemployability":441,"employability":988,"percentage_females":53.8,"percentage_single_parents":18.8,"percentage_foreign_citizenship":29.9},"inactive_beneficiaries_in_households":441,"basic_benefits_income":{"male":116,"female":132,"age_18_to_under_65":137,"age_65_and_above":164},"migration_background":{"foreign_citizenship":1623,"german_citizenship":2082}}}},{"district_id":12,"district_name":"Engelsby","district_detail":{"2021":{"residents":7536,"births":76,"age_ratio":38.7,"age_groups":{"age_18_to_under_30":1025,"age_30_to_under_45":1293,"age_45_to_under_65":2250,"age_65_to_under_80":1164,"age_0_to_under_7":497,"age_60_and_above":2292,"age_80_and_above":548,"age_to_under_18":1256,"age_18_to_under_65":4568,"age_65_and_above":1712},"employed_residents":2676,"unemployed_residents":276,"unemployment_characteristics":{"percentage_sgb_iii":33.0,"percentage_sgb_ii":67.0,"percentage_foreign_citizenship":25.0,"percentage_female":46.4,"percentage_age_under_25":10.5},"housing_benefit":247,"housing_assistance":{"notices_of_rent_arrears":null,"termination_rent_arrears":7,"termination_for_conduct":null,"action_for_eviction":12,"eviction_notice":7,"eviction_carried":5},"risk_of_homelessness":12,"benefits_age_15_to_under_65":{"employable_with_benefits":518,"unemployment_benefits":91,"basic_income":69,"assisting_benefits":15},"benefits_characteristics":{"beneficiaries_sgbii":754,"unemployability":236,"employability":518,"percentage_females":56,"percentage_single_parents":14.3,"percentage_foreign_citizenship":24.5},"inactive_beneficiaries_in_households":236,"basic_benefits_income":{"male":140,"female":161,"age_18_to_under_65":69,"age_65_and_above":99},"migration_background":{"foreign_citizenship":946,"german_citizenship":1335}}}},{"district_id":13,"district_name":"Tarup","district_detail":{"2021":{"residents":5596,"births":41,"age_ratio":27.3,"age_groups":{"age_18_to_under_30":808,"age_30_to_under_45":1106,"age_45_to_under_65":1528,"age_65_to_under_80":617,"age_0_to_under_7":453,"age_60_and_above":1214,"age_80_and_above":293,"age_to_under_18":1244,"age_18_to_under_65":3442,"age_65_and_above":910},"employed_residents":1972,"unemployed_residents":85,"unemployment_characteristics":{"percentage_sgb_iii":48.2,"percentage_sgb_ii":51.8,"percentage_foreign_citizenship":17.6,"percentage_female":54.1,"percentage_age_under_25":12.9},"housing_benefit":114,"housing_assistance":{"notices_of_rent_arrears":null,"termination_rent_arrears":null,"termination_for_conduct":null,"action_for_eviction":null,"eviction_notice":null,"eviction_carried":null},"risk_of_homelessness":null,"benefits_age_15_to_under_65":{"employable_with_benefits":120,"unemployment_benefits":41,"basic_income":23,"assisting_benefits":4},"benefits_characteristics":{"beneficiaries_sgbii":150,"unemployability":30,"employability":120,"percentage_females":50.8,"percentage_single_parents":9.2,"percentage_foreign_citizenship":23.3},"inactive_beneficiaries_in_households":30,"basic_benefits_income":{"male":77,"female":91,"age_18_to_under_65":23,"age_65_and_above":28},"migration_background":{"foreign_citizenship":244,"german_citizenship":865}}}}] \ No newline at end of file +{"detail":[{"births":31,"age_ratio":13.7,"residents":3866,"age_groups":{"age_to_under_18":360,"age_0_to_under_7":174,"age_60_and_above":565,"age_65_and_above":413,"age_80_and_above":148,"age_18_to_under_30":1338,"age_18_to_under_65":3093,"age_30_to_under_45":951,"age_45_to_under_65":804,"age_65_to_under_80":265},"birth_rate":28.3,"district_id":1,"district_name":"Altstadt","employment_rate":50.6,"housing_benefit":116,"employed_residents":1593,"housing_assistance":{"eviction_notice":5,"eviction_carried":5,"action_for_eviction":null,"notices_of_rent_arrears":null,"termination_for_conduct":null,"termination_rent_arrears":null},"migration_background":{"german_citizenship":375,"foreign_citizenship":999},"risk_of_homelessness":null,"unemployed_residents":226,"basic_benefits_income":{"male":245,"female":158,"age_65_and_above":55,"age_18_to_under_65":55},"benefits_characteristics":{"employability":377,"unemployability":98,"percentage_females":40.3,"beneficiaries_sgbii":475,"percentage_single_parents":7.2,"percentage_foreign_citizenship":31.3},"benefits_age_15_to_under_65":{"basic_income":55,"assisting_benefits":15,"unemployment_benefits":70,"employable_with_benefits":377},"unemployment_characteristics":{"percentage_female":41.2,"percentage_sgb_ii":69,"percentage_sgb_iii":31,"percentage_age_under_25":11.9,"percentage_foreign_citizenship":23},"inactive_beneficiaries_in_households":98},{"births":61,"age_ratio":10.3,"residents":4850,"age_groups":{"age_to_under_18":745,"age_0_to_under_7":347,"age_60_and_above":551,"age_65_and_above":369,"age_80_and_above":92,"age_18_to_under_30":1597,"age_18_to_under_65":3736,"age_30_to_under_45":1162,"age_45_to_under_65":977,"age_65_to_under_80":277},"birth_rate":47.2,"district_id":2,"district_name":"Neustadt","employment_rate":46,"housing_benefit":172,"employed_residents":1768,"housing_assistance":{"eviction_notice":9,"eviction_carried":5,"action_for_eviction":5,"notices_of_rent_arrears":null,"termination_for_conduct":null,"termination_rent_arrears":4},"migration_background":{"german_citizenship":443,"foreign_citizenship":1748},"risk_of_homelessness":5,"unemployed_residents":383,"basic_benefits_income":{"male":61,"female":49,"age_65_and_above":76,"age_18_to_under_65":122},"benefits_characteristics":{"employability":809,"unemployability":307,"percentage_females":41.9,"beneficiaries_sgbii":1116,"percentage_single_parents":7.5,"percentage_foreign_citizenship":40.5},"benefits_age_15_to_under_65":{"basic_income":122,"assisting_benefits":26,"unemployment_benefits":77,"employable_with_benefits":809},"unemployment_characteristics":{"percentage_female":34.7,"percentage_sgb_ii":79.9,"percentage_sgb_iii":20.1,"percentage_age_under_25":12,"percentage_foreign_citizenship":30.3},"inactive_beneficiaries_in_households":307},{"births":124,"age_ratio":23.8,"residents":12525,"age_groups":{"age_to_under_18":2164,"age_0_to_under_7":946,"age_60_and_above":2642,"age_65_and_above":1942,"age_80_and_above":448,"age_18_to_under_30":2349,"age_18_to_under_65":8419,"age_30_to_under_45":2722,"age_45_to_under_65":3348,"age_65_to_under_80":1494},"birth_rate":49.5,"district_id":3,"district_name":"Nordstadt","employment_rate":47.5,"housing_benefit":523,"employed_residents":4164,"housing_assistance":{"eviction_notice":20,"eviction_carried":10,"action_for_eviction":15,"notices_of_rent_arrears":null,"termination_for_conduct":4,"termination_rent_arrears":19},"migration_background":{"german_citizenship":1673,"foreign_citizenship":4109},"risk_of_homelessness":15,"unemployed_residents":823,"basic_benefits_income":{"male":112,"female":86,"age_65_and_above":248,"age_18_to_under_65":283},"benefits_characteristics":{"employability":1585,"unemployability":585,"percentage_females":48,"beneficiaries_sgbii":2170,"percentage_single_parents":14.5,"percentage_foreign_citizenship":37.5},"benefits_age_15_to_under_65":{"basic_income":283,"assisting_benefits":62,"unemployment_benefits":179,"employable_with_benefits":2},"unemployment_characteristics":{"percentage_female":42.5,"percentage_sgb_ii":78.3,"percentage_sgb_iii":21.7,"percentage_age_under_25":8.9,"percentage_foreign_citizenship":34.9},"inactive_beneficiaries_in_households":585},{"births":69,"age_ratio":43.2,"residents":8015,"age_groups":{"age_to_under_18":1241,"age_0_to_under_7":501,"age_60_and_above":2451,"age_65_and_above":1990,"age_80_and_above":714,"age_18_to_under_30":1387,"age_18_to_under_65":4784,"age_30_to_under_45":1485,"age_45_to_under_65":1912,"age_65_to_under_80":1276},"birth_rate":45,"district_id":4,"district_name":"Westliche Höhe","employment_rate":49.1,"housing_benefit":252,"employed_residents":2451,"housing_assistance":{"eviction_notice":7,"eviction_carried":5,"action_for_eviction":11,"notices_of_rent_arrears":null,"termination_for_conduct":null,"termination_rent_arrears":null},"migration_background":{"german_citizenship":991,"foreign_citizenship":1158},"risk_of_homelessness":11,"unemployed_residents":304,"basic_benefits_income":{"male":287,"female":244,"age_65_and_above":79,"age_18_to_under_65":74},"benefits_characteristics":{"employability":518,"unemployability":215,"percentage_females":49.4,"beneficiaries_sgbii":733,"percentage_single_parents":12.9,"percentage_foreign_citizenship":31.9},"benefits_age_15_to_under_65":{"basic_income":74,"assisting_benefits":27,"unemployment_benefits":96,"employable_with_benefits":518},"unemployment_characteristics":{"percentage_female":36.8,"percentage_sgb_ii":68.4,"percentage_sgb_iii":31.6,"percentage_age_under_25":11.5,"percentage_foreign_citizenship":27.3},"inactive_beneficiaries_in_households":215},{"births":55,"age_ratio":29.2,"residents":6644,"age_groups":{"age_to_under_18":816,"age_0_to_under_7":337,"age_60_and_above":1681,"age_65_and_above":1291,"age_80_and_above":451,"age_18_to_under_30":1578,"age_18_to_under_65":4537,"age_30_to_under_45":1376,"age_45_to_under_65":1583,"age_65_to_under_80":840},"birth_rate":36,"district_id":5,"district_name":"Friesischer Berg","employment_rate":53.7,"housing_benefit":258,"employed_residents":2517,"housing_assistance":{"eviction_notice":null,"eviction_carried":null,"action_for_eviction":6,"notices_of_rent_arrears":null,"termination_for_conduct":null,"termination_rent_arrears":4},"migration_background":{"german_citizenship":778,"foreign_citizenship":907},"risk_of_homelessness":6,"unemployed_residents":286,"basic_benefits_income":{"male":74,"female":79,"age_65_and_above":78,"age_18_to_under_65":88},"benefits_characteristics":{"employability":514,"unemployability":156,"percentage_females":52.3,"beneficiaries_sgbii":670,"percentage_single_parents":17.9,"percentage_foreign_citizenship":23.7},"benefits_age_15_to_under_65":{"basic_income":88,"assisting_benefits":40,"unemployment_benefits":99,"employable_with_benefits":514},"unemployment_characteristics":{"percentage_female":40.2,"percentage_sgb_ii":65.4,"percentage_sgb_iii":34.6,"percentage_age_under_25":15,"percentage_foreign_citizenship":21.7},"inactive_beneficiaries_in_households":156},{"births":55,"age_ratio":34.5,"residents":7472,"age_groups":{"age_to_under_18":1533,"age_0_to_under_7":549,"age_60_and_above":1958,"age_65_and_above":1484,"age_80_and_above":497,"age_18_to_under_30":917,"age_18_to_under_65":4455,"age_30_to_under_45":1433,"age_45_to_under_65":2105,"age_65_to_under_80":987},"birth_rate":45,"district_id":6,"district_name":"Weiche","employment_rate":54.6,"housing_benefit":163,"employed_residents":2566,"housing_assistance":{"eviction_notice":4,"eviction_carried":null,"action_for_eviction":null,"notices_of_rent_arrears":null,"termination_for_conduct":null,"termination_rent_arrears":null},"migration_background":{"german_citizenship":1222,"foreign_citizenship":933},"risk_of_homelessness":null,"unemployed_residents":205,"basic_benefits_income":{"male":76,"female":90,"age_65_and_above":45,"age_18_to_under_65":37},"benefits_characteristics":{"employability":324,"unemployability":170,"percentage_females":47.5,"beneficiaries_sgbii":494,"percentage_single_parents":16,"percentage_foreign_citizenship":38.9},"benefits_age_15_to_under_65":{"basic_income":37,"assisting_benefits":9,"unemployment_benefits":92,"employable_with_benefits":324},"unemployment_characteristics":{"percentage_female":42.4,"percentage_sgb_ii":55.1,"percentage_sgb_iii":44.9,"percentage_age_under_25":12.2,"percentage_foreign_citizenship":23.4},"inactive_beneficiaries_in_households":170},{"births":51,"age_ratio":25.3,"residents":4205,"age_groups":{"age_to_under_18":591,"age_0_to_under_7":283,"age_60_and_above":953,"age_65_and_above":714,"age_80_and_above":229,"age_18_to_under_30":996,"age_18_to_under_65":2900,"age_30_to_under_45":954,"age_45_to_under_65":950,"age_65_to_under_80":485},"birth_rate":54.2,"district_id":7,"district_name":"Südstadt","employment_rate":56.5,"housing_benefit":204,"employed_residents":1681,"housing_assistance":{"eviction_notice":null,"eviction_carried":null,"action_for_eviction":6,"notices_of_rent_arrears":null,"termination_for_conduct":null,"termination_rent_arrears":5},"migration_background":{"german_citizenship":517,"foreign_citizenship":919},"risk_of_homelessness":6,"unemployed_residents":226,"basic_benefits_income":{"male":37,"female":45,"age_65_and_above":65,"age_18_to_under_65":80},"benefits_characteristics":{"employability":444,"unemployability":171,"percentage_females":50.5,"beneficiaries_sgbii":615,"percentage_single_parents":16.2,"percentage_foreign_citizenship":29.3},"benefits_age_15_to_under_65":{"basic_income":80,"assisting_benefits":17,"unemployment_benefits":59,"employable_with_benefits":444},"unemployment_characteristics":{"percentage_female":43.8,"percentage_sgb_ii":73.9,"percentage_sgb_iii":26.1,"percentage_age_under_25":10.2,"percentage_foreign_citizenship":23.9},"inactive_beneficiaries_in_households":171},{"births":58,"age_ratio":22.1,"residents":6702,"age_groups":{"age_to_under_18":657,"age_0_to_under_7":291,"age_60_and_above":1380,"age_65_and_above":1062,"age_80_and_above":435,"age_18_to_under_30":2225,"age_18_to_under_65":4983,"age_30_to_under_45":1333,"age_45_to_under_65":1425,"age_65_to_under_80":627},"birth_rate":34.3,"district_id":8,"district_name":"Sandberg","employment_rate":51.2,"housing_benefit":252,"employed_residents":2598,"housing_assistance":{"eviction_notice":8,"eviction_carried":6,"action_for_eviction":null,"notices_of_rent_arrears":null,"termination_for_conduct":null,"termination_rent_arrears":5},"migration_background":{"german_citizenship":601,"foreign_citizenship":1117},"risk_of_homelessness":null,"unemployed_residents":327,"basic_benefits_income":{"male":75,"female":70,"age_65_and_above":74,"age_18_to_under_65":81},"benefits_characteristics":{"employability":567,"unemployability":139,"percentage_females":39.2,"beneficiaries_sgbii":706,"percentage_single_parents":10.1,"percentage_foreign_citizenship":25.4},"benefits_age_15_to_under_65":{"basic_income":81,"assisting_benefits":28,"unemployment_benefits":100,"employable_with_benefits":567},"unemployment_characteristics":{"percentage_female":33.6,"percentage_sgb_ii":69.4,"percentage_sgb_iii":30.6,"percentage_age_under_25":15,"percentage_foreign_citizenship":19.3},"inactive_beneficiaries_in_households":139},{"births":73,"age_ratio":24.9,"residents":8371,"age_groups":{"age_to_under_18":959,"age_0_to_under_7":402,"age_60_and_above":1938,"age_65_and_above":1446,"age_80_and_above":449,"age_18_to_under_30":2160,"age_18_to_under_65":5966,"age_30_to_under_45":1735,"age_45_to_under_65":2071,"age_65_to_under_80":997},"birth_rate":37.3,"district_id":9,"district_name":"Jürgensby","employment_rate":52.6,"housing_benefit":328,"employed_residents":3222,"housing_assistance":{"eviction_notice":4,"eviction_carried":4,"action_for_eviction":8,"notices_of_rent_arrears":4,"termination_for_conduct":null,"termination_rent_arrears":14},"migration_background":{"german_citizenship":699,"foreign_citizenship":1273},"risk_of_homelessness":8,"unemployed_residents":427,"basic_benefits_income":{"male":77,"female":78,"age_65_and_above":104,"age_18_to_under_65":126},"benefits_characteristics":{"employability":837,"unemployability":211,"percentage_females":43.8,"beneficiaries_sgbii":1048,"percentage_single_parents":9.3,"percentage_foreign_citizenship":26.8},"benefits_age_15_to_under_65":{"basic_income":126,"assisting_benefits":36,"unemployment_benefits":116,"employable_with_benefits":837},"unemployment_characteristics":{"percentage_female":38.6,"percentage_sgb_ii":72.8,"percentage_sgb_iii":27.2,"percentage_age_under_25":14.3,"percentage_foreign_citizenship":21.8},"inactive_beneficiaries_in_households":211},{"births":59,"age_ratio":22.1,"residents":6794,"age_groups":{"age_to_under_18":1017,"age_0_to_under_7":425,"age_60_and_above":2113,"age_65_and_above":1644,"age_80_and_above":578,"age_18_to_under_30":974,"age_18_to_under_65":4133,"age_30_to_under_45":1296,"age_45_to_under_65":1863,"age_65_to_under_80":1066},"birth_rate":48,"district_id":10,"district_name":"Fruerlund","employment_rate":56.1,"housing_benefit":370,"employed_residents":2406,"housing_assistance":{"eviction_notice":null,"eviction_carried":null,"action_for_eviction":null,"notices_of_rent_arrears":null,"termination_for_conduct":null,"termination_rent_arrears":null},"migration_background":{"german_citizenship":927,"foreign_citizenship":645},"risk_of_homelessness":null,"unemployed_residents":303,"basic_benefits_income":{"male":111,"female":119,"age_65_and_above":110,"age_18_to_under_65":138},"benefits_characteristics":{"employability":569,"unemployability":223,"percentage_females":56.1,"beneficiaries_sgbii":792,"percentage_single_parents":20.7,"percentage_foreign_citizenship":19},"benefits_age_15_to_under_65":{"basic_income":138,"assisting_benefits":28,"unemployment_benefits":93,"employable_with_benefits":569},"unemployment_characteristics":{"percentage_female":45.5,"percentage_sgb_ii":69.3,"percentage_sgb_iii":30.7,"percentage_age_under_25":6.9,"percentage_foreign_citizenship":17.5},"inactive_beneficiaries_in_households":223},{"births":145,"age_ratio":52.8,"residents":15301,"age_groups":{"age_to_under_18":2361,"age_0_to_under_7":943,"age_60_and_above":5460,"age_65_and_above":4386,"age_80_and_above":1596,"age_18_to_under_30":1976,"age_18_to_under_65":8554,"age_30_to_under_45":2609,"age_45_to_under_65":3969,"age_65_to_under_80":2790},"birth_rate":58,"district_id":11,"district_name":"Mürwik","employment_rate":52.9,"housing_benefit":484,"employed_residents":4740,"housing_assistance":{"eviction_notice":5,"eviction_carried":5,"action_for_eviction":10,"notices_of_rent_arrears":5,"termination_for_conduct":null,"termination_rent_arrears":6},"migration_background":{"german_citizenship":2082,"foreign_citizenship":1623},"risk_of_homelessness":10,"unemployed_residents":532,"basic_benefits_income":{"male":116,"female":132,"age_65_and_above":164,"age_18_to_under_65":137},"benefits_characteristics":{"employability":988,"unemployability":441,"percentage_females":53.8,"beneficiaries_sgbii":1429,"percentage_single_parents":18.8,"percentage_foreign_citizenship":29.9},"benefits_age_15_to_under_65":{"basic_income":137,"assisting_benefits":28,"unemployment_benefits":197,"employable_with_benefits":988},"unemployment_characteristics":{"percentage_female":48.7,"percentage_sgb_ii":63,"percentage_sgb_iii":37,"percentage_age_under_25":10.9,"percentage_foreign_citizenship":20.9},"inactive_beneficiaries_in_households":441},{"births":76,"age_ratio":38.7,"residents":7536,"age_groups":{"age_to_under_18":1256,"age_0_to_under_7":497,"age_60_and_above":2292,"age_65_and_above":1712,"age_80_and_above":548,"age_18_to_under_30":1025,"age_18_to_under_65":4568,"age_30_to_under_45":1293,"age_45_to_under_65":2250,"age_65_to_under_80":1164},"birth_rate":60.7,"district_id":12,"district_name":"Engelsby","employment_rate":56,"housing_benefit":247,"employed_residents":2676,"housing_assistance":{"eviction_notice":7,"eviction_carried":5,"action_for_eviction":12,"notices_of_rent_arrears":null,"termination_for_conduct":null,"termination_rent_arrears":7},"migration_background":{"german_citizenship":1335,"foreign_citizenship":946},"risk_of_homelessness":12,"unemployed_residents":276,"basic_benefits_income":{"male":140,"female":161,"age_65_and_above":99,"age_18_to_under_65":69},"benefits_characteristics":{"employability":518,"unemployability":236,"percentage_females":56,"beneficiaries_sgbii":754,"percentage_single_parents":14.3,"percentage_foreign_citizenship":24.5},"benefits_age_15_to_under_65":{"basic_income":69,"assisting_benefits":15,"unemployment_benefits":91,"employable_with_benefits":518},"unemployment_characteristics":{"percentage_female":46.4,"percentage_sgb_ii":67,"percentage_sgb_iii":33,"percentage_age_under_25":10.5,"percentage_foreign_citizenship":25},"inactive_beneficiaries_in_households":236},{"births":41,"age_ratio":27.3,"residents":5596,"age_groups":{"age_to_under_18":1244,"age_0_to_under_7":453,"age_60_and_above":1214,"age_65_and_above":910,"age_80_and_above":293,"age_18_to_under_30":808,"age_18_to_under_65":3442,"age_30_to_under_45":1106,"age_45_to_under_65":1528,"age_65_to_under_80":617},"birth_rate":37,"district_id":13,"district_name":"Tarup","employment_rate":53.8,"housing_benefit":114,"employed_residents":1972,"housing_assistance":{"eviction_notice":null,"eviction_carried":null,"action_for_eviction":null,"notices_of_rent_arrears":null,"termination_for_conduct":null,"termination_rent_arrears":null},"migration_background":{"german_citizenship":865,"foreign_citizenship":244},"risk_of_homelessness":null,"unemployed_residents":85,"basic_benefits_income":{"male":77,"female":91,"age_65_and_above":28,"age_18_to_under_65":23},"benefits_characteristics":{"employability":120,"unemployability":30,"percentage_females":50.8,"beneficiaries_sgbii":150,"percentage_single_parents":9.2,"percentage_foreign_citizenship":23.3},"benefits_age_15_to_under_65":{"basic_income":23,"assisting_benefits":4,"unemployment_benefits":41,"employable_with_benefits":120},"unemployment_characteristics":{"percentage_female":54.1,"percentage_sgb_ii":51.8,"percentage_sgb_iii":48.2,"percentage_age_under_25":12.9,"percentage_foreign_citizenship":17.6},"inactive_beneficiaries_in_households":30}],"summary":{"sum_residents":97877,"sum_districts_area":null}} From d8e8863bee5da5d1243d0014456ed20ac8777928 Mon Sep 17 00:00:00 2001 From: Aurelius Wendelken Date: Wed, 4 Oct 2023 17:01:53 +0200 Subject: [PATCH 7/8] added npm clean script and linked it --- package.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index f76b380..fced0fa 100644 --- a/package.json +++ b/package.json @@ -5,8 +5,9 @@ "source": "src/index.html", "browserslist": "> 0.5%, last 2 versions, not dead", "scripts": { - "start": "parcel serve --no-cache", - "build": "parcel build --public-url '.' --no-cache", + "clean": "rm -r dist .parcel-cache", + "start": "pnpm run clean && parcel serve --no-cache", + "build": "pnpm run clean && parcel build --public-url '.'", "htmlhint": "htmlhint \"**/*.html\" --ignore \"dist/**/*.html\" --format unix", "prettier": "npm:@yuhr/prettier", "preinstall": "cp scripts/* .git/hooks", From 5c9307b82be960d8aa1a29d5b717470efb975dd3 Mon Sep 17 00:00:00 2001 From: Roald Christesen Date: Wed, 4 Oct 2023 18:21:13 +0200 Subject: [PATCH 8/8] enabled temporary button functionality --- src/components/demoComponent.js | 2 +- src/main.js | 13 ++++--------- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/src/components/demoComponent.js b/src/components/demoComponent.js index 3ab5e51..891ef81 100644 --- a/src/components/demoComponent.js +++ b/src/components/demoComponent.js @@ -94,7 +94,7 @@ export default class DemoComponent extends Component { const mean = sum / barsCount const h = 1 const x = 0 - const y = sum / barsCount / maxValue * this.height + const y = this.height - mean / maxValue * this.height const x1 = 0 const x2 = this.floatToString(this.width) diff --git a/src/main.js b/src/main.js index 3c6041c..2ab5844 100644 --- a/src/main.js +++ b/src/main.js @@ -32,7 +32,7 @@ app.configurate({ 'locale': 'de-DE' }) const fastButtonSection = new Grid(view, 'fast-button-section', { 'classList': 'section arena padding-10' }) for (let i = 1; i <= 13; i++) { - new Button(fastButtonSection, `district-button-${i}`, { 'classList': '', 'label': i, 'tag': i - 1, 'onClick': button2OnClick }) + new Button(fastButtonSection, `district-button-${i}`, { 'classList': '', 'label': i, 'tag': i, 'onClick': button2OnClick }) } new Text(view, 'text-distict-details', { 'classList': 'supertext', 'html': '

District' }) @@ -79,6 +79,7 @@ app.configurate({ 'locale': 'de-DE' }) new DemoComponent(subView2, 'svg2', { 'classList': 'custom-demo-component', 'width': 200, 'height': 100, 'values': [10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 10, 1000, 30] }) } + // Section Geburten { new Text(view, 'text-4', { 'classList': 'supertext', 'html': '

Geburten' }) @@ -96,18 +97,11 @@ app.configurate({ 'locale': 'de-DE' }) } - - - - - - app.buildView('root') // Init app, must be called after configurate and buildView app.initApp('./details.json', 13) - // Handlers function button1OnClick(component) { for (let i = 2; i <= 5; i++) { @@ -118,8 +112,9 @@ function button1OnClick(component) { } } - function button2OnClick(component) { + app.onDistrictChanged(component.tag - 1) + console.debug(app.view.countDescendants()) for (let i = 2; i <= 5; i++) { const s = app.componentById(`section-${i}`) if (s !== undefined) {