| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436 |
- <sampling-app>
- <header app-title="{ props.header.title }"
- description="{ props.header.description }"
- investment="{ props.investment }"
- periods="{ props.periods }"
- period="{ state.sPeriod }"
- on-period-change="{ onPeriodChange }"
- on-investment-input-change="{ onInvestmentInputChange }"
- on-investment-output-change="{ onInvestmentOutputChange }"
- labels="{ props.labels }"
- is="sampling-header">
- </header>
- <section class="samplers">
- <div each="{ sampler in this.samplers }"
- class="sampler-wrapper sampler-wrapper-{ sampler.id }">
- <div is="sampler-controls"
- class="reference-group"
- investment="{ props.investment }"
- referenceClasses="{ props.referenceClasses }"
- on-option-change="{ onOptionChange }"
- on-ratio-change="{ onRatioChange }"
- sampler="{ sampler }">
- </div>
- <sampler-visual class="sampler-visual"
- on-mouse-over="{ onMouseOver }"
- on-mouse-out="{ onMouseOut }"
- mouse-over="{ state.mouseOver }"
- classes="{ props.classes }"
- colours="{ state.colours }"
- investment-output="{ state.investment.output.value }"
- sampler="{ sampler }">
- </sampler-visual>
- </div>
- </section>
- <section class="textual controls">
- <div class="buttons size-selectors">
- <div each="{ gsize in props.groupSizes }"
- class="size-input">
- <input id="size-{ gsize.id }"
- type="radio"
- class="input-radio"
- value="{ gsize.id }"
- defaultChecked="{ gsize.id === state.sGroupSize.id }"
- name="groupSize"
- onchange="{ groupSizeChanged }" />
- <label for="size-{ gsize.id }" class="form-item">{ gsize.name }</label>
- </div>
- </div>
- <sampler-textual each="{ sampler in this.samplers }"
- id="sampler-textual-{ sampler.id }"
- class="sampler-textual sampler-textual-{ sampler.id }"
- sampler="{ sampler }"
- group-size-id="{ state.sGroupSize.id }"
- textual="{ props.textual }"
- colours="{ state.colours }"
- investment-output="{ state.investment.output.value }"
- mouse-over="{ state.mouseOver }">
- </sampler-textual>
- <div class="sample-button" onclick="{ sampleButtonClicked }">
- <button class="sample"
- disabled="{ maximumSamplesDrawn }">
- <a href="#">{ props.labels.sample } { state.investment.output.value } { props.labels.currency }?</a>
- </button>
- </div>
- <div class="buttons export-reset">
- <button id="export"
- class=" export form-item"
- disabled="{ state.isLoading }"
- onclick="{ generatePDF }">
- <a href="{ state.pdfDownloadURL }" id="exportPDF" class="export" download="{ state.pdfFilename }">
- <i class="demo-icon icon-download"></i>Faktenbox
- </a>
- </button>
- <button class="reset form-item"
- onclick="{ onReset }"
- disabled="{ resetEnabled }">
- <a href="#"><i class="demo-icon icon-ccw"></i>{ props.labels.reset }</a>
- </button>
- </div>
- </section>
- <script charset="utf-8">
- import '@babel/polyfill';
- import * as riot from 'riot';
- import * as d3 from '../d3custom';
- import { Sampler } from '../sampler';
- import { PDFExport } from '../pdfExport';
- import visual_config from './config_visual.json';
- import info from './sampling-info.riot'; // built from /data/sampling-info.md by bin/preprocess.js
- import header from './sampling-header.riot';
- import textual from './sampler-textual.riot';
- import controls from './sampler-controls.riot';
- import visual from './sampler-visual.riot';
- riot.register('sampling-header', header);
- riot.register('sampling-info', info);
- riot.register('sampler-controls', controls);
- riot.register('sampler-textual', textual);
- riot.register('sampler-visual', visual);
- export default {
- // life cycle methods
- onBeforeMount(props, state) {
- state.investment = props.investment;
- state.period = props.period;
- this.state.isLoading = false;
- // load defaults: selected values / elements
- state.mouseOver = false;
- state.sPeriod = props.periods[props.defaults.periodId];
- state.sGroupSize = props.groupSizes[props.defaults.groupSizeId];
- state.colours = props.classes.map(c => c.colour);
- this.fetchData(props.dataFilePath);
- document.title = props.header.title;
- },
- // fetch all data once
- fetchData(filepath) { //-> initialise, create samplers, samplers call controller.filterData(s.o1, s.o2)
- d3.tsv(filepath).then(this.initialise);
- },
- // filter data for sampler with primaryKeys, make sure there is also data for opposing sampler (secondaryKeys)
- filterData(primaryKeys, secondaryKeys) {
- function parseVal(val) {
- return parseFloat(val.replace(',', '.'));
- }
- let filteredData = this.data
- .filter(
- d => d[primaryKeys[0]] && d[primaryKeys[1]] &&
- d[secondaryKeys[0]] && d[secondaryKeys[1]]) // only return data if row contains values for both options for both samplers
- .map(d => {
- return {
- year: d.date_of_investment,
- values: [parseVal(d[primaryKeys[0]]), parseVal(d[primaryKeys[1]])]
- }
- });
- return filteredData;
- },
- // Create new data set for sampler identified by samplerId
- queryData(samplerId, leftOptions, rightOptions) {
- return this.filterData(
- [
- this.createDataKey(leftOptions[samplerId].name),
- this.createDataKey(rightOptions[samplerId].name)
- ], [
- this.createDataKey(leftOptions[(samplerId + 1) % 2].name),
- this.createDataKey(rightOptions[(samplerId + 1) % 2].name)
- ]
- );
- },
- createDataKey(name) {
- return this.state.sPeriod.name + '_' + name;
- },
- getLeftOptions() {
- if (this.samplers === undefined) {
- return [
- this.props.investment.options[this.props.defaults.selectedOptionsIds[0][0]],
- this.props.investment.options[this.props.defaults.selectedOptionsIds[1][0]]
- ];
- }
- return [
- this.samplers[0].investment.options[0],
- this.samplers[1].investment.options[0]
- ];
- },
- getRightOptions() {
- if (this.samplers === undefined) {
- return [
- this.props.investment.options[this.props.defaults.selectedOptionsIds[0][1]],
- this.props.investment.options[this.props.defaults.selectedOptionsIds[1][1]]
- ];
- }
- return [
- this.samplers[0].investment.options[1],
- this.samplers[1].investment.options[1]
- ];
- },
- getRatio(i) {
- if(this.samplers === undefined) {
- return this.props.defaults.ratios[i];
- }
- return this.samplers[i].investment.ratio;
- },
- createSamplers() {
- let samplers = [];
- let leftOptions = this.getLeftOptions();
- let rightOptions = this.getRightOptions();
- for (let i = 0; i < 2; ++i) {
- samplers.push(new Sampler(
- i,
- this.queryData(i, leftOptions, rightOptions),
- this.props.classes,
- {
- ratio: this.getRatio(i),
- inverseRatio: 1 - this.getRatio(i), // inverted <input type="range"> bar!!
- input: this.props.investment.input,
- output: this.props.investment.output,
- options: [leftOptions[i], rightOptions[i]]
- },
- this.props.maxSamples
- ));
- }
- return samplers;
- },
- initialise(data) {
- this.data = data;
- this.samplers = this.createSamplers();
- // define getters for template updating only after 'this.props' is defined
- Object.defineProperty(this, 'maximumSamplesDrawn', { get: this.maximumSamplesDrawn });
- Object.defineProperty(this, 'resetEnabled', { get: this.resetEnabled });
- this.resetSamplers();
- },
- drawSamples(amount, samplers) {
- if (samplers[0].data.length !== samplers[1].data.length) {
- throw new Error('Data arrays of samplers are not of equal size: ' + samplers[0].data.length + ', ' + samplers[1].data.length);
- }
- for (let i = 0; i < amount; ++i) {
- // Importance sampling implementation
- let cumulatedProb = 0.0;
- let random = Math.random();
- let prob = 1.0 / samplers[0].data.length;
- let index = 0;
- for (; index < samplers[0].data.length; ++index) {
- cumulatedProb += prob;
- if (random < cumulatedProb) {
- break;
- }
- }
- for (let sampler of samplers) {
- sampler.drawSample(index);
- }
- }
- },
- clearSamples() {
- for (let sampler of this.samplers) {
- sampler.reset();
- }
- },
- // Generates pdf on button click
- generatePDF(e) {
- if (e.hasBeenProcessed === true) { // Discard event and do nothing if the event already has been processed
- e.stopImmediatePropagation();
- this.$('#exportPDF').classList.remove('loading');
- this.update({
- isLoading: false
- });
- return;
- }
- this.$('#exportPDF').classList.add('loading');
- this.update({
- isLoading: true
- });
- e.stopPropagation(); // Stop event propagation
- let pdfSamplers = this.createSamplers();
- this.drawSamples(this.props.maxSamples, pdfSamplers);
- // Create new PDFExport and pass all necessary data and the click event
- this.pdfx = new PDFExport(
- this,
- {
- title: this.props.header.title,
- clickEvent: e,
- description: this.props.header.description,
- referenceClasses: this.props.referenceClasses,
- author: this.props.author,
- subject: this.props.subject,
- classes: this.props.classes,
- colours: this.state.colours,
- context: this.props.context,
- fontRatios: this.props.fontRatios,
- iconArray: {
- blocks: visual_config.blocks,
- dotspacingRatio: visual_config.dotspacingRatio,
- radius: visual_config.radius
- },
- investment: this.props.investment,
- lang: this.lang,
- labels: this.props.labels,
- maxSamples: this.props.maxSamples,
- samplers: pdfSamplers,
- selectedPeriod: this.state.sPeriod
- }
- );
- },
- /**
- * Called from pdf generation code
- * The event that has been triggered to start the pdf generation process
- * is dispatched so that the pdf download happens after the variables have been assigned
- *
- * @param url: Blob url
- * @param e: Event object
- */
- updatePDFLink(url, e) {
- this.update({
- pdfDownloadURL: url,
- pdfFilename: `${this.props.header.title}.pdf`
- });
- let anchor = this.$('#exportPDF');
- anchor.classList.remove('loading');
- e.hasBeenProcessed = true; // indicate that the event has been processed and should be discarded (otherwise the pdf would be downloaded indefinitely)
- anchor.dispatchEvent(e);
- this.update({
- isLoading: false
- });
- },
- resetSamplers() {
- this.clearSamples();
- this.update();
- },
- maximumSamplesDrawn() {
- let sampler = this.samplers[0]; // Both samplers always have the same amount of samples and maxSamples
- return sampler.samples.length === sampler.maxSamples;
- },
- resetEnabled() {
- let result = false;
- if (this.samplers !== undefined) {
- result = this.samplers[0].samples.length === 0;
- }
- return result;
- },
- sampleButtonClicked(e) {
- e.preventDefault();
- let groupSize = this.state.sGroupSize.value;
- let remaining = this.samplers[0].maxSamples - this.samplers[0].samples.length;
- let amount = Math.min(remaining, groupSize);
-
- this.drawSamples(amount, this.samplers);
- this.update();
- },
-
- groupSizeChanged(e) {
- this.state.sGroupSize = this.props.groupSizes[parseInt(e.target.value)];
- this.update();
- },
- // events
- onPeriodChange (e) {
- this.state.sPeriod = this.props.periods[e.target.selectedIndex];
- for (let sampler of this.samplers) {
- sampler.data = this.queryData(sampler.id, this.getLeftOptions(), this.getRightOptions());
- }
- this.resetSamplers();
- },
- onMouseOver (e, idx) {
- this.state.mouseOver = true;
- for (let sampler of this.samplers) {
- sampler.setCurrentSample(idx);
- }
- this.update();
- },
- onMouseOut (e, idx) {
- this.state.mouseOver = false;
- for (let sampler of this.samplers) {
- sampler.setCurrentSample(sampler.samples.length - 1);
- }
- this.update();
- },
- onInvestmentInputChange (e) {
- let newVal = Math.max(Math.min(e.target.valueAsNumber, this.state.investment.input.max), this.state.investment.input.min);
- this.state.investment.input.value = newVal;
- this.resetSamplers();
- },
- onInvestmentOutputChange (e) {
- let newVal = Math.max(Math.min(e.target.valueAsNumber, this.state.investment.output.max), this.state.investment.output.min);
- this.state.investment.output.value = newVal
- this.resetSamplers();
- },
- onOptionChange(e, sampler) {
- let leftOptions = this.getLeftOptions();
- let rightOptions = this.getRightOptions();
- // Set new data for both samplers, as data arrays must be of equal lengths
- for (let s of this.samplers) {
- s.data = this.queryData(s.id, leftOptions, rightOptions);
- }
- this.resetSamplers();
- },
- onRatioChange(e) {
- this.resetSamplers();
- },
- onReset(e) {
- e.preventDefault();
- this.resetSamplers();
- }
- }
- </script>
- </sampling-app>
|