Skip to content

Commit

Permalink
Merge pull request #152 from nzzdev/release-4.0.0
Browse files Browse the repository at this point in the history
Release 4.0.0
  • Loading branch information
Philip Küng authored Apr 29, 2021
2 parents d41128a + 24a6f3a commit 065e632
Show file tree
Hide file tree
Showing 49 changed files with 5,051 additions and 599 deletions.
19 changes: 14 additions & 5 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,22 @@
"type": "node",
"request": "launch",
"name": "Launch Program",
"program": "${workspaceFolder}/index.js"
"program": "${workspaceFolder}/dev.js",
"runtimeVersion": "12.17.0"
},
{
"type": "node",
"request": "launch",
"name": "run 2e2 test",
"program": "${workspaceFolder}/test/e2e-tests.js"
}
"name": "run e2e test",
"program": "${workspaceFolder}/test/e2e-tests.js",
"runtimeVersion": "12.17.0",
},
{
"type": "node",
"request": "launch",
"name": "run dom test",
"program": "${workspaceFolder}/test/dom-tests.js",
"runtimeVersion": "12.17.0",
},
]
}
}
136 changes: 118 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ Footnotes are a feature to display annotations in the table and the sources in t
{
type: "numeric",
value: "3",
classes: ["q-table-col-footnotes-cardlayout-single"],
classes: ["q-table-footnote-col-cardlayout-single"],
footnote: {
value: 2,
unicode: "²",
Expand All @@ -175,28 +175,13 @@ Footnotes are a feature to display annotations in the table and the sources in t

###### Implementation details frontend

- The `value` of the cell will be displayed inside a `span`-element with the class `q-table-annotation`
- The `value` of the cell will be displayed inside a `span`-element with the class `q-table-footnote-annotation`
- The `span`-element has the dataset `data-annotation` and the value `cell.footnote.value` applied to it
- With the `::after` pseudo element, the dataset `data-annotation` will then be applied after the value
- For the sources of the annotations the `footnotes` array applied to the `context` will be looped and displayed in the footer
- If the option `colorColumn` is selected, the footnote will be dispalyed in a seperate element and the color of the footnote will be set to `opacity: 0.65`

[to the top](#table-of-contents)

#### Table search

<img src="/doc/table-search.png" align="right" width=300 height=400>

This feature makes large tables searchable.

###### Implementation details
- By default the table is collapsed and the `q-table_show-more-button` is visible at the bottom of the table ([see Collapsable table](#collapsable-table)).
- When the user starts typing, all rows will be made visible and the `q-table_show-more-button` disappears.
- The actual search function triggers, as soon as the user types the second character.
- The filter searches through text-based columns only.
- When the user deletes his input, the table collapses and the `q-table_show-more-button` will be visible again.

[to the top](#table-of-contents)

### Options

#### hideTableHeader
Expand All @@ -211,8 +196,19 @@ This options allows to hide the header of each column. By default it's `false`

#### showTableSearch

<img src="/doc/table-search.png" align="right" width=300 height=400>

This option allows to show or hide the table search feature. The option is only available, when there are more than 16 rows in the table. Default value is `false`.

###### Implementation details
- By default the table is collapsed and the `q-table_show-more-button` is visible at the bottom of the table ([see Collapsable table](#collapsable-table)).
- When the user starts typing, all rows will be made visible and the `q-table_show-more-button` disappears.
- The actual search function triggers, as soon as the user types the second character.
- The filter searches through text-based columns only.
- When the user deletes his input, the table collapses and the `q-table_show-more-button` will be visible again.

[to the top](#table-of-contents)

###### Implementation details

- If the option is used, the input element for the table search won't be rendered.
Expand Down Expand Up @@ -283,6 +279,110 @@ Minibars are a visual feature to display the difference between numbers in the t

[to the top](#table-of-contents)


#### colorColumn

<img src="/doc/colorColumn.png" align="right" width=427 height=202>

This feature allows to select a column and colorize it. There are two types:
- `numerical`
- `categorical`

##### Numerical

When selecting the numerical option, the numbers inside the column will be split in buckets. Depending on the `bucketType`, the buckets will be calculated differently.
This option will be displayed with columns containing numerical values only, otherwise an error will be displayed.

#### Label

The label allows to select between three options:
- `noLabel` (default)
- `average`
- `median`

This value will then be displayed on the legend.

###### BucketType

There are four different buckets and each of it comes with it's own properties:

- Ckmeans, `numberBuckets`
- Quantile, `numberBuckets`
- Equal, `numberBuckets`
- Custom, , `numberBuckets` and `customBuckets`

###### Scale

The scale can be chosen from between two types:
- `sequential`
- `diverging`

###### Custom Color

When having the expert-role added, the user is able to adjust the colors of the buckets.

When selecting the `diverging`-option, the options from which border it will be spilt are dynamically listed too.

##### Categorical

When selecting the categorical option, the values of the column will be mapped to categories. The categories will then be colored according to the color-scale.

##### Legend

The way the legend will be displayed is depending on the `colorColumnType`. When using numerical-option, the range of the values will be calculated by buckets, which can be changed on the options. The lowest and highest value will be displayed on the left and right end of the legend.

Depending on the selected bucketing method, the legend will be displayed differently. The array passed to render the legend looks as following:

```javascript
buckets = [
{
from, // lowest bucket border value
to, // highest bucket border value
color, // color depending on the selected color schema
},
]
```

If there's the case that one of the bucket has just a single value in it, the single bucket will be displayed below with a seperate icon. If there is an entry without a value, there will be an extra icon too, for displaying 'no data'.

The categorical legend will simply map the values to their colors.

###### Implementation details serverside


- Just like the feature minibars, the `option-availability` route will check if there are at least 3 columns to display this option\
- If the option is selected, through the `dynamic-schema` route, depending on which `colorColumnType` is selected, will display specific options
- **Important**: The function `getColorColumnContext()` will always return an object, when minibars aren't used the object is empty
- The function `getColorColumnContext()` uses three parameters: `colorColumn`(option), `data`(data from table) and `width`(content-width)
- If the option is selected, the function `getColorColumnContext()` will always return this object
```javascript
{
categoricalOptions, // object
colorColumnType, // string
colors, // array
legendData, // object
numericalOptions, // object
selectedColumn, // number
}
```

- Depending on the selected `colorColumnType`, either `numerical` or `categorical`, some of the properties will be added
- **Important**: The properties `categoricalOptions` and `numericalOptions` will always be an object, even if not selected
- If selecting the `colorColumnType` `numerical`, the following properties will be added
```javascript
{
methodBox, // object
formattedValue, // array
}
```

###### Implementation details frontend

- Either `numerical` or `categorical`, both table-cells will be colored with the reference to the `colors` array
- The legend will be displayed 100% on `desktop` and `mobile`, on `fullwidth` it will be displayed `640px` (size of the text in article)
- When resizing the graphic, the `EventLister` on the event `resize` implemented in the `renderColorColumnNumericalLegend()` will be triggered
- The legend will then be resized according to the `width`

#### Display options

Display options can be set before embedding the graphic in the article.
Expand Down
6 changes: 6 additions & 0 deletions dev.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
process.env.METHOD_BOX_ARTICLE = `{
"title": "Mehr zur Datenberechnung der NZZ",
"url": "https://www.nzz.ch/ld.1580452"
}`;

require("./index.js");
Binary file added doc/colorColumn.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
19 changes: 19 additions & 0 deletions helpers/colorColomnMethodBox.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
const methodBoxTextConfig = {
ckmeans:
"Die unterschiedlich grossen Gruppen kommen durch ein statistisches Verfahren zustande, welches die Werte so in Gruppen einteilt, dass die Unterschiede zwischen den Regionen möglichst gut sichtbar werden (Jenks Natural Breaks).",
quantile:
"Die Gruppen wurden so gewählt, dass in jeder Gruppe möglichst gleich viele Werte vorhanden sind.",
equal:
"Die Gruppen wurden so gewählt, dass sie jeweils einen gleich grossen Bereich auf der Skala abdecken.",
custom: "Die Gruppen wurden manuell definiert.",
};

function getMethodBoxInfo(bucketType) {
const methodBoxText = methodBoxTextConfig[bucketType];
return {
text: methodBoxText || "",
article: { "title": "Mehr zur Datenberechnung der NZZ", "url": "https://www.nzz.ch/ld.1580452" }
};
}

module.exports = { getMethodBoxInfo };
79 changes: 79 additions & 0 deletions helpers/colorColumn.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
const dataHelpers = require("./data.js");
const legendHelpers = require("./colorColumnLegend.js");
const colorHelpers = require("./colorColumnColor.js");
const methodBoxHelpers = require("./colorColomnMethodBox.js");

function hasCustomBuckets(bucketType) {
return bucketType === "custom";
}

function getNumberBuckets(colorColumn) {
try {
if (colorColumn.numericalOptions.bucketType !== "custom") {
return colorColumn.numericalOptions.numberBuckets;
} else {
const bucketBorderValues = getCustomBucketBorders(
colorColumn.numericalOptions.customBuckets
);
return bucketBorderValues.length - 1; // min value is part of border values and has to be excluded here
}
} catch {
return 0;
}
}

function getColorColumnContext(colorColumn, data, width) {
let colorColumnContext = {};
if (colorColumn !== null && colorColumn !== undefined && colorColumn.selectedColumn !== null && colorColumn.selectedColumn !== undefined) {
let colors = [];
if (colorColumn.colorColumnType === "numerical") {

let formattingOptions = {
maxDigitsAfterComma: dataHelpers.getMaxDigitsAfterCommaInDataByRow(
data, colorColumn.selectedColumn
),
roundingBucketBorders: colorColumn.numericalOptions.bucketType !== "custom"
};

colorColumnContext.legendData = legendHelpers.getNumericalLegend(
data,
colorColumn,
formattingOptions.maxDigitsAfterComma,
width
);

colorColumnContext.methodBox = methodBoxHelpers.getMethodBoxInfo(
colorColumn.numericalOptions.bucketType
);

let valuesByColumn = dataHelpers.getNumericalValuesByColumn(data, colorColumn.selectedColumn);
colorColumnContext.formattedValues = [];
colorColumnContext.methodBox.formattedBuckets = dataHelpers.getFormattedBuckets(formattingOptions, colorColumnContext.legendData.buckets);
valuesByColumn.map(value => {
let color = colorHelpers.getColor(value, colorColumnContext.legendData);
colors = [...colors, color];
colorColumnContext.formattedValues = [...colorColumnContext.formattedValues, dataHelpers.getFormattedValue(formattingOptions, value)];
})
} else {
colorColumnContext.legendData = legendHelpers.getCategoricalLegend(
data,
colorColumn
);

let categoriesByColumn = dataHelpers.getCategoricalValuesByColumn(data, colorColumn.selectedColumn);
categoriesByColumn.map(category => {
let color = colorHelpers.getColor(category, colorColumnContext.legendData);
colors = [...colors, color];
});
}
colorColumnContext = { ...colorColumnContext, ...colorColumn, colors };
}
return colorColumnContext;
}


module.exports = {
getNumberBuckets,
hasCustomBuckets,
getColorColumnContext,
};
Loading

0 comments on commit 065e632

Please sign in to comment.