Commit 21a93c82 authored by Rodrigo Tapia-McClung's avatar Rodrigo Tapia-McClung

Remove unused pgserver components

parent 3d536e0c
// based on https://raw.githubusercontent.com/tobinbradley/dirt-simple-postgis-http-api/master/routes/bbox.js
const sqlTableName = require('./utils/sqltablename.js');
function splitTableName(params) {
const parts = params.table.split('.');
if (parts.length === 1) {
return {
namespace: 'public',
tablename: parts[0]
}
} else {
return {
namespace: parts[0],
tablename: parts[1]
}
}
}
const getSqlEstimateRows = (params) =>{
const ts = splitTableName(params);
return `select ((c.reltuples/case when c.relpages=0 then 1 else c.relpages end) * (pg_relation_size(c.oid) / (current_setting('block_size')::integer)))::integer as estimated_rows
from pg_class c JOIN pg_namespace n on c.relnamespace = n.oid
where n.nspname='${ts.namespace}' and c.relname='${ts.tablename}'
`;
}
const sqlEstimateBbox = (params, query, estimatedRows) => {
const ts = splitTableName(params);
return `
with srid as
(select st_srid(${query.geom_column}) srid, geometrytype(${query.geom_column}) geomtype
from ${sqlTableName(params.table)}
where ${query.geom_column} is not null limit 1)
,bboxsrid as
(select ST_EstimatedExtent('${ts.namespace}', '${ts.tablename}', '${query.geom_column}') as bboxsrid)
,bboxll as
(select st_extent(st_transform(st_setsrid(st_envelope(bboxsrid), srid),4326)) bboxll
from bboxsrid, srid)
select ${estimatedRows} as allrows, ${estimatedRows} as geomrows,bboxll,srid,bboxsrid,geomtype
from bboxll,srid,bboxsrid
`;
}
const sqlBbox = (params, query) => {
return `
with srid as
(select st_srid(${query.geom_column}) srid, geometrytype(${query.geom_column}) geomtype
from ${sqlTableName(params.table)}
where ${query.geom_column} is not null limit 1)
,bboxll as
(select ST_Extent(ST_Transform(${query.geom_column}, 4326)) as bboxll, count(*) allrows, count(${query.geom_column}) geomrows
from ${sqlTableName(params.table)}
-- Optional where filter
${query.filter ? `WHERE ${query.filter}` : '' }
)
,bboxsrid as
(select st_extent(st_transform(st_setsrid(st_envelope(bboxll),4326),srid)) bboxsrid
from bboxll,srid)
select allrows, geomrows, bboxll,srid,bboxsrid,geomtype
from bboxll,srid,bboxsrid
`;
}
module.exports = function(app, pool, cache) {
let cacheMiddleware = async(req, res, next) => {
if (!cache) {
next();
return;
}
const cacheDir = `${req.params.table}/bbox`
const key = ((req.query.geom_column?req.query.geom_column:'geom')
+ (req.query.columns?'_'+req.query.columns:'')
+ (req.query.filter?'_'+req.query.filter:''))
.replace(/[\W]+/g, '_');
const bbox = await cache.getCachedFile(cacheDir, key);
if (bbox) {
console.log(`bbox cache hit for ${cacheDir}?${key}`);
res.header('Content-Type', 'application/json').send(bbox);
return;
} else {
res.sendResponse = res.send;
res.send = (body) => {
if (res.statusCode >= 200 && res.statusCode < 300) {
cache.setCachedFile(cacheDir, key, body);
}
res.sendResponse(body);
}
next();
}
}
/**
* @swagger
*
* /api/bbox/{table}:
* get:
* description: Gets the bounding box of a feature(s)
* tags: ['api']
* produces:
* - application/json
* parameters:
* - name: table
* description: name of postgis table
* in: path
* required: true
* type: string
* - name: geom_column
* description: name of geometry column (default 'geom')
* in: query
* required: false
* - name: filter
* type: string
* description: 'Optional filter parameters for a SQL WHERE statement.'
* in: query
* required: false
* responses:
* 200:
* description: boundingboxes and row counters
* content:
* application/json
* schema:
* type: object
* properties:
* estimated:
* type: boolean
* description: result is estimated (true) or precise (false)
* allrows:
* type: integer
* description: number of rows in table
* geomrows:
* type: integer
* description: number of non-null geometries in table
* bboxll:
* description: boundingbox of geometries defined in latitude and longitude
* type: array
* minItems: 2
* maxItems: 2
* items:
* type: array
* minItems: 2
* maxItems: 2
* items:
* type: number
* srid:
* type: integer
* description: id of spatial reference system (EPSG code) used by geometries
* bboxsrid:
* description: boundingbox of geometries defined using geometry srid
* type: array
* minItems: 2
* maxItems: 2
* items:
* type: array
* minItems: 2
* maxItems: 2
* items:
* type: number
* 422:
* description: invalid datasource or columnname
*/
app.get('/api/bbox/:table', cacheMiddleware, async (req, res)=>{
if (!req.query.geom_column) {
req.query.geom_column = 'geom'; //default
}
if (!req.query.srid) {
req.query.srid = 4326
}
let sql = getSqlEstimateRows(req.params);
let estimated = false;
try {
const estimateResult = await pool.query(sql);
const estimatedRows = estimateResult.rows[0].estimated_rows;
if (estimatedRows < 5000000 || req.query.filter) {
sql = sqlBbox(req.params, req.query);
} else {
sql = sqlEstimateBbox(req.params, req.query, estimatedRows);
estimated = true;
}
const result = await pool.query(sql);
if (result.rows.length === 1) {
const row = result.rows[0];
res.json({
estimated: estimated,
allrows: Number(row.allrows),
geomtype: row.geomtype,
geomrows: Number(row.geomrows),
srid: row.srid,
bboxll: row.bboxll?row.bboxll.match(/BOX\((.*)\)/)[1].split(',').map(coord=>coord.split(' ').map(c=>parseFloat(c))):null,
bboxsrid: row.bboxsrid?row.bboxsrid.match(/BOX\((.*)\)/)[1].split(',').map(coord=>coord.split(' ').map(c=>parseFloat(c))):null
})
} else if (result.rows.length === 0) {
res.json({
allrows: 0,
geomrows: 0,
bboxll: null,
srid: 0,
bboxsrid: null
})
} else {
throw(new Error('bbox query returned more than 1 row'));
}
} catch(err) {
console.log(err);
res.status(500).json({error: err.message});
}
})
}
\ No newline at end of file
const sqlTableName = require('./utils/sqltablename.js');
// get top 100 most frequent values and null values
const sql = (params, query) => {
return `
with sortedrecords as
(select count(1)::integer as "count", "${params.column}" as "value"
from ${sqlTableName(params.table)}
where ${query.geom_column} is not null
group by "${params.column}" order by count(1) desc)
,toprecords as
(select * from sortedrecords limit 100)
,resultset as
(select * from toprecords
union
select * from sortedrecords where value is null)
select distinct * from resultset order by count desc;`
} // TODO, use sql place holders $1, $2 etc. instead of inserting user-parameters into query
// https://leafo.net/guides/postgresql-calculating-percentile.html
const sqlPercentiles = (params, query) => {
return `
select min(buckets.value) "from", max(buckets.value) "to", count(ntile)::integer "count", ntile as percentile
from
(select "${params.column}" as value, ntile(100) over (order by "${params.column}")
from ${sqlTableName(params.table)}
where "${params.column}" is not null and ${query.geom_column} is not null)
as buckets
group by ntile order by ntile;
`
}
const sqlPercentilesBoolean = (params, query) => {
return `
select case when min(buckets.value) = 0 then false else true end "from", case when max(buckets.value) = 0 then false else true end "to", count(ntile)::integer "count", ntile as percentile
from
(select "${params.column}"::integer as value, ntile(100) over (order by "${params.column}")
from ${sqlTableName(params.table)}
where "${params.column}" is not null and ${query.geom_column} is not null)
as buckets
group by ntile order by ntile;
`
}
let typeMap = null;
async function getTypeName(id, pool) {
if (!typeMap) {
const sql = "select oid,typname from pg_type where oid < 1000000 order by oid";
try {
const queryResult = await pool.query(sql);
typeMap = new Map(queryResult.rows.map(row=>[row.oid, row.typname]));
} catch(err) {
console.log(`error loading types: ${err}`);
return id.toString();
}
}
const result = typeMap.get(id);
if (!result) {
return id.toString();
}
return result;
}
module.exports = function(app, pool, cache) {
let cacheMiddleWare = async(req, res, next) => {
if (!cache) {
next();
return;
}
const cacheDir = `${req.params.table}/colstats/`;
const key = ((req.query.geom_column?req.query.geom_column:'geom') + (req.params.column?','+req.params.column:''))
.replace(/[\W]+/g, '_');
const stats = await cache.getCachedFile(cacheDir, key);
if (stats) {
console.log(`stats cache hit for ${cacheDir}?${key}`);
if (stats.length === 0) {
res.status(204)
}
res.header('Content-Type', 'application/json').send(stats);
return;
} else {
res.sendResponse = res.send;
res.send = (body) => {
if (res.statusCode >= 200 && res.statusCode < 300) {
cache.setCachedFile(cacheDir, key, body);
}
res.sendResponse(body);
}
next();
}
}
/**
* @swagger
*
* /data/{table}/colstats/{column}:
* get:
* description: get statistics for column
* tags: ['meta']
* produces:
* - application/json
* parameters:
* - name: table
* description: name of postgis table
* in: path
* required: true
* type: string
* - name: column
* description: name of column
* in: path
* type: string
* required: true
* - name: geom_column
* description: name of geometry column (default 'geom')
* in: query
* required: false
* responses:
* 200:
* description: json statistics
* content:
* application/json
* schema:
* type: object
* properties:
* table:
* type: string
* description: name of table
* column:
* type: string
* description: name of column
* numvalues:
* description: number of different values, null means unknown (>2000)
* type: integer
* uniquevalues:
* description: whether or not all values are unique
* type: boolean
* values:
* description: array of values sorted by highest count first
* type: array
* maxItems: 2000
* items:
* type: object
* properties:
* value:
* description: encountered value for column (any type)
* type: string
* count:
* description: number of geometries with this value
* type: integer
* 204:
* description: no data
* 422:
* description: invalid table or column
* 500:
* description: unexpected error
*/
app.get('/data/:table/colstats/:column', cacheMiddleWare, async (req, res)=>{
if (!req.query.geom_column) {
req.query.geom_column = 'geom'; // default
}
let sqlString = sql(req.params, req.query);
//console.log(sqlString);
try {
let queryResult = await pool.query(sqlString);
let datatype = await getTypeName(queryResult.fields[1].dataTypeID, pool);
if (datatype === "numeric" || datatype === "int8") {
// numeric datatype, try to convert to Number
try {
queryResult.rows = queryResult.rows.map(row=>{row.value=row.value?Number(row.value):row.value; return row});
} catch(err) {
// failed Numeric conversion
}
}
const stats = queryResult.rows
const result = {
table: req.params.table,
column: req.params.column,
datatype: datatype,
numvalues: stats.length < 2000?stats.length:null,
uniquevalues: stats.length?stats[0].value !== null?stats[0].count === 1:stats.length>1?stats[1].count === 1:false:[],
values: stats
}
if (stats.length === 0) {
result.percentiles = [];
res.json(result);
return;
}
if (datatype === "bool") {
sqlString = sqlPercentilesBoolean(req.params, req.query);
} else {
sqlString = sqlPercentiles(req.params, req.query);
}
queryResult = await pool.query(sqlString);
if (datatype === "numeric" || datatype === "int8") {
// numeric datatype, try to convert to Number
try {
queryResult.rows = queryResult.rows.map(row=>{row.from=Number(row.from); row.to=Number(row.to); return row});
} catch(err) {
// failed Numeric conversion
}
}
result.percentiles = queryResult.rows;
res.json(result);
} catch(err) {
console.log(err);
let status = 500;
switch (err.code) {
case '42P01':
// table does not exist
status = 422;
break;
case '42703':
// column does not exist
status = 422;
break;
default:
}
res.status(status).json({error:err.message})
}
})
}
\ No newline at end of file
const sql = (params) => {
return `
SELECT
attname as field_name,
typname as field_type
FROM
pg_namespace, pg_attribute, pg_type, pg_class
WHERE
pg_type.oid = atttypid AND
pg_class.oid = attrelid AND
relnamespace = pg_namespace.oid AND
attnum >= 1 AND
relname = '${params.table}'
${params.schema?` AND nspname= '${params.schema}'`:''}
`
}
module.exports = function (app, pool) {
/**
* @swagger
*
* /data/layer_columns/:table:
* get:
* description: Returns a list of columns in the specified table.
* tags: ['meta']
* summary: 'list table columns'
* produces:
* - application/json
* parameters:
* - name: table
* description: The name of the table
* in: path
* required: true
* type: string
* responses:
* 200:
* description: list of columns (names and types)
* 422:
* description: table not found or not accessible
*/
app.get('/data/layer_columns/:table', async (req, res)=> {
let tableName, schemaName;
const table = req.params.table;
if (table) {
const parts = table.split('.');
if (parts.length === 1) {
tableName = parts[0];
schemaName = null;
} else {
schemaName = parts[0];
tableName = parts[1];
}
req.params.table = tableName;
req.params.schema = schemaName;
const sqlString = sql(req.params, req.query);
try {
const result = await pool.query(sqlString);
res.json(result.rows);
} catch (err) {
console.log(err);
let status = 500;
switch (err.code) {
case '42P01':
// table does not exist
status = 422;
break;
case '42703':
// column does not exist
status = 422;
break;
default:
}
res.status(status).json({error:err.message})
}
} else {
res.status(422).json({error:"missing parameter 'table'"})
}
});
}
\ No newline at end of file
function login(app) {
/**
* @swagger
*
* /login:
* post:
* description: Login to the application
* produces:
* - application/json
* parameters:
* - name: username
* description: Username to use for login.
* in: formData
* required: true
* type: string
* - name: password
* description: User's password.
* in: formData
* required: true
* type: string
* responses:
* 200:
* description: login
*/
app.post('/login', (req, res) => {
// Your implementation comes here ...
});
app.get('/login', (req, res) => {
res.json({result:"ok"})
});
}
module.exports = login
\ No newline at end of file
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>PGServer Info</title>
<style>
.medium {width: 300px; height: auto;}
#mapcontainer {
display: flex;
flex-wrap: wrap;
}
#map {display: inline-block; width: 500px; height: 500px; border: 1 px solid lightblue;}
#legendcontainer {display: flex;}
#legend {min-width: 250px;}
#featurecontainer {
display: flex;
flex-direction: column;
justify-content: space-between;
}
#featureinfo {
display: inline-block;
max-width: 400px;
}
#graphs {
display: flex;
flex-wrap: wrap;
}
#graphs div {
margin: 5px;
}
#colorschemes {
display: flex;
align-content: center;
}
#colorschemes div {
padding: 2px;
margin: 2px;
cursor: pointer;
}
#colorschemes div:hover {
background-color: lightgray;
}
#colorschemes div.selected {
background-color: darkgray;
}
#colorschemes svg {
display: block;
}
#colorschemes rect {
stroke: #333;
stroke-width: 0.5;
}
</style>
<script src="https://cdn.jsdelivr.net/npm/chart.js@2.8.0"></script>
<script src='https://api.tiles.mapbox.com/mapbox-gl-js/v1.2.0/mapbox-gl.js'></script>
<link href='https://api.tiles.mapbox.com/mapbox-gl-js/v1.2.0/mapbox-gl.css' rel='stylesheet' />
<link href='loader.css' rel='stylesheet' />
<script src="./colorbrewer.js"></script>
<script src="./ticks.js"></script>
<script>
"use strict";
let map = null;
let globalStats = null;
let selectedColorScheme = 0;
function init() {
const urlParams = new URLSearchParams(window.location.search);
const fullTableName = urlParams.get('table');
const attrName = urlParams.get("column");
const attrType = urlParams.get("columntype");
const geomType = urlParams.get('geomtype');
const geomColumn = urlParams.get('geom_column');
document.querySelector('#tablename').innerHTML = `Layer: ${fullTableName}<br>Geometry: ${geomColumn}`;
document.querySelector('#columnname').innerHTML = `Attribute: ${attrName} (${attrType})`;
document.querySelector('#back').innerHTML = `<a href="tableinfo.html?table=${fullTableName}&geom_column=${geomColumn}">Back to layer info</a>`;
document.querySelectorAll('[name="colorscheme"]').forEach(radio=>radio.onchange=updateLegendColorSchemes);
document.querySelectorAll('[name="classtype"]').forEach(radio=>radio.onchange=applyLegendToMap);
document.querySelector('#classcount').onchange = updateLegendColorSchemes;
document.querySelector('input[name="colorsreversed"]').onchange = updateLegendColorSchemes
initMap();
fetch(`data/${fullTableName}/colstats/${attrName}?geom_column=${geomColumn}`).then(response=>{
const loadingElement = document.querySelector('#loader');
loadingElement.innerHTML = "";
if (!response.ok) {
try {
response.json(json=>loadingElement.innerHtml = json.error);
} catch(err) {
loadingElement.innerHTML = err;
}
return;
}
response.json()
.then(json=>{
if (json.datatype === 'timestamptz' || json.datatype === 'date') {
json.percentiles = json.percentiles.map(percentile=>{
percentile.from = percentile.from?new Date(percentile.from):null;
percentile.to = percentile.to?new Date(percentile.to):null;
return percentile;
})
json.values = json.values.map(value=>{
value.value = value.value?new Date(value.value):null;
return value;
})
}
graphStats(json)
globalStats = json;
})
.catch(err=>
loadingElement.innerHTML = `Failed to parse response, message: ${err.message}`
);
})
}
function initMap()
{
const urlParams = new URLSearchParams(window.location.search);
const fullTableName = urlParams.get('table');
const geomType = urlParams.get('geomtype');
const geomColumn = urlParams.get('geom_column');
const attrName = urlParams.get("column");
const mapDefinition = {
container: 'map',
"style": {
"version": 8,
"name": "DefaultBaseStyle",
"id": "defaultbasestyle",
"glyphs": `https://free.tilehosting.com/fonts/{fontstack}/{range}.pbf?key=`,
"sources": {
"osmgray": {
"type":"raster",
"tileSize":256,
"tiles":[
"https://tiles.edugis.nl/mapproxy/osm/tiles/osmgrayscale_EPSG900913/{z}/{x}/{y}.png?origin=nw"
],
"attribution":"&copy; <a href=\"https://www.openstreetmap.org/about\" target=\"copyright\">OpenStreetMap contributors</a>",
"maxzoom":19
}
},
"layers": [
{
"id": "osmgray",
"type": "raster",
"source": "osmgray"
}
]
}
}
const bboxll = urlParams.get('bboxll');
if (bboxll) {
mapDefinition.bounds = JSON.parse(bboxll);
}
map = new mapboxgl.Map(mapDefinition);
map.on('load', () => {
let layerType, paint;
switch (geomType) {
case 'MULTIPOLYGON':
case 'POLYGON':
layerType = 'fill';
paint = {
"fill-color": "red",
"fill-outline-color": "white",
"fill-opacity": 0.8
}
break;
case 'MULTILINESTRING':
case 'LINESTRING':
layerType = 'line';
paint = {
"line-color": "red",
"line-width": 1
}
break;
case 'MULTIPOINT':
case 'POINT':
layerType = "circle";
paint = {
"circle-radius": 5,
"circle-color": "red",
"circle-stroke-color": "white",
"circle-stroke-width" : 1
}
break;
default:
break;
}
if (!layerType) {
document.querySelector("#layerjson").innerHTML = `Field geom of type: '${geomType}' not supported<br>Supported types: (MULTI-) POINT/LINE/POLYGON<p>`
} else {
const baseUrl = new URL(`/data`, window.location.href).href;
const layer = {
"id": "attrlayer",
"type": layerType,
"source": {
"type": "vector",
"tiles": [`${baseUrl}/${fullTableName}/mvt/{z}/{x}/{y}?geom_column=${geomColumn}&columns=${attrName}&include_nulls=0`],
},
"source-layer": fullTableName,
"paint": paint,
//"filter": ['has', attrName]
}
map.addLayer(layer);
addLegendLine('red', fullTableName, layerType);
updateLayerJsonDisplay();
}
})
map.on('mousemove', function (e) {
var features = map.queryRenderedFeatures(e.point).map(function(feature){ return {layer: {id: feature.layer.id, type: feature.layer.type}, properties:(feature.properties)};});
document.querySelector('#featureinfo').innerHTML = JSON.stringify(features.map(feature=>feature.properties), null, 2);
});
}
function updateLayerJsonDisplay() {
const layerjson = map.getStyle().layers.filter(l=>l.id==='attrlayer')[0];
layerjson.source = map.getSource(layerjson.source).serialize();
document.querySelector("#layerjson").innerHTML = `<pre>${JSON.stringify(layerjson, null, 2)}</pre>`;
}
function prepareGraphColors(classType) {
globalStats.graphColors = [];
globalStats.classType = classType;
}
function addGraphColors(color, upperValue) {
globalStats.graphColors.push({color: color, upperValue: upperValue});
}
let graphNoData = null;
let graphValues = null;
let graphMostFrequent = null;
function destroyGraphs() {
if (graphNoData) {
graphNoData.destroy();
}
if (graphValues) {
graphValues.destroy();
}
if (graphMostFrequent) {
graphMostFrequent.destroy();
}
}
function graphStats(stats) {
destroyGraphs();
const nullValues = stats.values.filter(value=>value.value === null).reduce((result, value)=>result+value.count,0);
const rowCount = stats.percentiles.reduce((result, percentile)=>result + percentile.count, 0);
graphNoData = new Chart(document.querySelector('#graphnodata canvas'), {
type: 'doughnut',
data: {
labels: ['no data','data',],
datasets: [{
backgroundColor: ['lightgray', 'red'],
borderColor: 'white',
borderWidth: 0,
data: [nullValues, rowCount]
}]
}
});
if (stats.percentiles.length && typeof(stats.percentiles[0].from) !== 'string') {
graphValues = new Chart(document.querySelector('#graphvalues canvas'), {
type: 'line',
data: {
labels: stats.percentiles.map((percentile, index, arr)=>Math.round((index/(arr.length - 1)) * 100)),
datasets: [{
backgroundColor: stats.graphColors? stats.percentiles.map((percentile)=>{
let color = stats.classType === 'mostfrequent' ?
stats.graphColors.find(graphColor=>percentile.to == graphColor.upperValue) :
stats.graphColors.find(graphColor=>percentile.to < graphColor.upperValue);
if (!color) {
color = stats.graphColors[stats.graphColors.length - 1];
}
return color.color;
}):'red',
//borderColor: 'lighgray',
data: stats.percentiles.map((percentile,index,arr)=>(index===arr.length-1)?percentile.to:percentile.from),
fill: false
}]
},
options : {
title: {
display: false,
text : 'some title'
},
legend: {
display: false
},
scales: {
yAxes: [
{
scaleLabel: {
display: true,
labelString: stats.column
}
}
],
xAxes: [
{
scaleLabel: {
display: true,
labelString: 'percentage of rows',
padding: 0
}
}
]
}
}
})
}
const valuesSummary = stats.values.filter(value=>value.value !== null).slice(0,10);
const valuesSummeryRowCount = valuesSummary.reduce((result, value)=>result+value.count,0);
if (rowCount > valuesSummeryRowCount) {
valuesSummary.push({
value:"other",
count: rowCount - valuesSummeryRowCount
})
}
graphMostFrequent = new Chart(document.querySelector('#graphmostfrequent canvas'), {
type: "horizontalBar",
data: {
labels: valuesSummary.map(value=>value.value),
datasets: [
{
backgroundColor: stats.graphColors? valuesSummary.map((value)=>{
let color = stats.graphColors.find(graphColor=>value.value === graphColor.upperValue)
if (!color) {
color = stats.graphColors[stats.graphColors.length - 1];
}
return color.color;
}):'red',
data: valuesSummary.map(value=>value.count)
}
]
},
options : {
legend: {
display: false,
},
scales: {
xAxes: [
{
ticks: {
min: 0
}
}
]
}
}
})
}
function addLegendLine(color, label, type) {
if (!type) {
type = 'fill';
}
const legend = document.querySelector('#legend');
let svg;
switch (type) {
case 'fill':
svg = `<svg width="30" height="15">
<rect width="30" height="15" style="fill:${color};fill-opacity:1;stroke-width:1;stroke:#444"></rect>
</svg>`
break;
case 'line':
svg = `<svg width="30" height="15">
<line x1="0" y1="15" x2="30" y2="0" style="stroke:${color};stroke-width:${color.width};" />
</svg>`
break;
case 'circle':
svg = `<svg width="12" height="12">
<circle cx="6" cy="6" r="5" style="fill:${color};fill-opacity:1 stroke-width:1;stroke:white" />
</svg>`
}
const legendLine = document.createElement('div');
legendLine.innerHTML = `<div> <span>${svg}</span> <span>${label}<span></div>`
legend.appendChild(legendLine);
}
function prepareLegend() {
if (globalStats) {
document.querySelector('#legend').innerHTML = '';
return true;
}
let messageElem = document.querySelector('#legend.message');
if (messageElem) {
return false;
}
messageElem = document.createElement('div');
messageElem.classList.add('message');
messageElem.innerHTML = "waiting for stats, retry later...";
return false;
}
// schemeTypes 'div', 'qual', 'seq'
// for diverging, qualitative and sequential legends
function getColorSchemes(numClasses, schemeType, reversed) {
for (; numClasses > 2; numClasses--) {
let result = colorbrewer.filter(scheme=>scheme.type===schemeType && scheme.sets.length > numClasses - 3)
.map(scheme=>{
let result = Object.assign({}, scheme.sets[numClasses - 3]);
result.name = scheme.name;
result.type = scheme.type;
return result;
});
if (result.length) {
if (reversed) {
result.forEach(scheme=>scheme.colors = scheme.colors.map(c=>c).reverse())
}
return result;
}
}
if (numClasses === 2) {
let result = colorbrewer.filter(scheme=>scheme.type===schemeType)
.map(scheme=>{
const result = {colors: [scheme.sets[0].colors[0],scheme.sets[0].colors[2]]}
result.name = scheme.name;
result.type = scheme.type;
return result;
});
if (reversed) {
result.forEach(scheme=>scheme.colors = scheme.colors.map(c=>c).reverse());
}
return result;
}
return [{colors:['#ff0000']}];
}
function selectColorScheme(schemeIndex)
{
let schemeDiv = document.querySelector('#colorschemes');
schemeDiv.querySelectorAll('div').forEach(div=>div.classList.remove('selected'));
schemeDiv.querySelectorAll('div').forEach((div,index)=>{if (index === schemeIndex) {div.classList.add('selected')}});
selectedColorScheme = schemeIndex;
applyLegendToMap();
}
// display the available colorschemes for current scheme type and class number
function updateLegendColorSchemes() {
let schemeDiv = document.querySelector('#colorschemes');
schemeDiv.innerHTML = '';
let classCount = Number(document.querySelector('#classcount').value);
let schemeType = document.querySelector('input[name="colorscheme"]:checked').value;
let reversed = document.querySelector('input[name="colorsreversed"]').checked;
let schemes = getColorSchemes(classCount, schemeType, reversed);
classCount = schemes[0].colors.length;
if (selectedColorScheme > schemes.length - 1) {
selectedColorScheme = schemes.length - 1;
}
schemes.forEach((scheme, schemeIndex) => {
let ramp = document.createElement('div');
if (schemeIndex === selectedColorScheme) {
ramp.classList.add('selected');
selectedColorScheme = schemeIndex;
}
let svg = `<svg width="15" height="${classCount * 15}">${
scheme.colors.map((color, index)=>`<rect fill="${color}" width="15" height="15" y="${index * 15}"></rect>`).join('\n')
}</svg>`;
ramp.innerHTML = svg;
ramp.onclick = ()=>selectColorScheme(schemeIndex);
schemeDiv.appendChild(ramp);
})
applyLegendToMap();
}
function applyLegendToMap() {
let classType = document.querySelector('input[name="classtype"]:checked').value;
let reversed = document.querySelector('input[name="colorsreversed"]').checked;
if (prepareLegend()) {
prepareGraphColors(classType);
let classCount = Number(document.querySelector('#classcount').value);
if (classCount === 1) {
// special case, single classification
} else {
// classCount > 1
let layerType = map.getLayer('attrlayer').type;
let rowCount = globalStats.percentiles.reduce((result, percentile)=>result + percentile.count, 0);
let mapboxPaint;
let schemeType = document.querySelector('input[name="colorscheme"]:checked').value;
switch(classType) {
case 'mostfrequent':
let schemes = getColorSchemes(classCount, schemeType, reversed);
classCount = schemes[0].colors.length;
let classValues = globalStats.values.filter(value=>value.value !== null);
let needsSlice = classValues.length > classCount;
if (needsSlice) {
classValues = classValues.slice(0, classCount - 1);
let classValuesRowCount = classValues.reduce((result, value)=>result+value.count,0);
classValues.push({
value:"other",
count: rowCount - classValuesRowCount
})
}
if (globalStats.datatype === 'numeric') {
mapboxPaint = [
"match",
["to-number", ["get",`${globalStats.column}`], 0]
];
} else {
mapboxPaint = [
"match",
["get",`${globalStats.column}`]
];
}
classValues.forEach((value, index) => {
addLegendLine(schemes[selectedColorScheme].colors[index], value.value, layerType);
addGraphColors(schemes[selectedColorScheme].colors[index], value.value)
if (index < classValues.length - 1 || !needsSlice) {
mapboxPaint.push(value.value);
mapboxPaint.push(schemes[selectedColorScheme].colors[index]);
}
});
mapboxPaint.push(schemes[selectedColorScheme].colors[classValues.length -1]);
break;
case 'quantile':
let percentileBreaks = globalStats.percentiles.reduce((result, percentile)=>{
if (result.length === 0) {
result.push(Object.assign({}, percentile)); // spread operator not supported by current Edge
return result;
}
if (result[result.length - 1].from === percentile.from) {
result[result.length - 1].to = percentile.to;
result[result.length - 1].count += percentile.count;
return result;
}
result.push(Object.assign({}, percentile));
return result;
},[]);
let seqSchemes = getColorSchemes(classCount, schemeType, reversed);
classCount = seqSchemes[selectedColorScheme].colors.length;
if (classCount > percentileBreaks.length) {
classCount = percentileBreaks.length
} else {
let totalRowCount = percentileBreaks.reduce((result, percentile)=>result+percentile.count, 0);
let rowCountPerClass = totalRowCount / classCount;
let sumRowCount = 0;
let sumClassCount = 0
percentileBreaks = percentileBreaks.reduce((result, percentile)=>{
sumRowCount += percentile.count;
if (sumRowCount > sumClassCount * rowCountPerClass && result.length < classCount) {
// new class
result.push(percentile);
sumClassCount++;
} else {
result[result.length - 1].to = percentile.to;
result[result.length - 1].count += percentile.count;
}
return result;
},[])
}
mapboxPaint = ["case"]
percentileBreaks.forEach((brk, index, arr)=>{
addLegendLine(seqSchemes[selectedColorScheme].colors[index], `${brk.from} - ${brk.to}`, layerType);
addGraphColors(seqSchemes[selectedColorScheme].colors[index], brk.to);
if (globalStats.datatype === 'numeric') {
mapboxPaint.push(["<",["to-number", ["get", `${globalStats.column}`], 0],brk.to],seqSchemes[selectedColorScheme].colors[index]);
} else {
mapboxPaint.push(["<",["get", `${globalStats.column}`],brk.to],seqSchemes[selectedColorScheme].colors[index]);
}
})
mapboxPaint.push(seqSchemes[selectedColorScheme].colors[classCount - 1])
break;
case 'interval':
let min = globalStats.percentiles[0].from;
let max = globalStats.percentiles[globalStats.percentiles.length - 1].to;
if (typeof min === "number") {
let intervalSchemes = getColorSchemes(classCount, schemeType, reversed);
classCount = intervalSchemes[0].colors.length;
let classTicks = getIntervalClassTicks(min, max, classCount);
mapboxPaint = ["case"];
classTicks.classes.forEach((classStart, index)=>{
let legendFrom = classStart;
let legendTo = (index === classCount - 1 ? classTicks.max : classTicks.classes[index + 1]);
if (globalStats.datatype === 'int4' || globalStats.datatype === 'int8') {
legendFrom = Math.floor(legendFrom);
legendTo = Math.floor(legendTo);
}
addLegendLine(intervalSchemes[selectedColorScheme].colors[index], `${(legendFrom)} - ${legendTo}`, layerType);
addGraphColors(intervalSchemes[selectedColorScheme].colors[index], legendTo)
if (globalStats.datatype === 'numeric') {
// convert javscript string to number
mapboxPaint.push(["<", ["to-number",["get", globalStats.column], 0],legendTo], intervalSchemes[selectedColorScheme].colors[index]);
} else {
mapboxPaint.push(["<", ["get", globalStats.column],legendTo], intervalSchemes[selectedColorScheme].colors[index]);
}
})
mapboxPaint.push(intervalSchemes[selectedColorScheme].colors[classCount - 1])
}
break;
}
if (mapboxPaint) {
let colorprop = `${layerType}-color`;
map.setPaintProperty('attrlayer', colorprop, mapboxPaint);
updateLayerJsonDisplay();
graphStats(globalStats);
}
}
let nullValues = globalStats.values.filter(value=>value.value === null).reduce((result, value)=>result+value.count,0);
let checkButtonNullValues = document.querySelector('#hidenulls')
if (nullValues) {
checkButtonNullValues.removeAttribute('disabled');
} else {
checkButtonNullValues.setAttribute('disabled', '')
}
}
}
</script>
</head>
<body onload="init()">
<h1>Attribute information</h1>
<h2 id="tablename"></h2>
<h2 id="columnname"></h2>
<div id="loader">
<h2>Loading statistics...</h2>
<div class="loader">
<div class="dot"></div><div class="dot"></div><div class="dot"></div><div class="dot"></div>
<div class="dot"></div><div class="dot"></div><div class="dot"></div><div class="dot"></div>
</div>
</div>
<div id="graphs">
<div id="graphnodata" class="canvascontainer medium"><canvas></canvas></div>
<div id="graphvalues" class="canvascontainer medium"><canvas></canvas></div>
<div id="graphmostfrequent" class="canvascontainer medium"><canvas></canvas></div>
</div>
<div id="mapcontainer">
<div id="map"></div>
<div id="featurecontainer">
<div id="legendcontainer">
<div id="legend"></div>
<div id="legendformcontainer">
<select id="classcount" name="classcount">
<option value="1" selected>1</option>
<option value="2">2</option>
<option value="3">3</option>
<option value="4">4</option>
<option value="5">5</option>
<option value="6">6</option>
<option value="7">7</option>
<option value="8">8</option>
<option value="9">9</option>
<option value="10">10</option>
<option value="11">11</option>
<option value="12">12</option>
</select><label for="numclasses">number of classes</label><br>
Classification methods:<br>
<input type="radio" name="classtype" id="interval" value="interval"><label for="interval">equal interval</label>
<input type="radio" name="classtype" id="quantile" value="quantile" checked><label for="quantile">quantile</label>
<input type="radio" name="classtype" id="mostfrequent" value="mostfrequent"><label for="mostfrequent">most frequent</label><br>
Color schemes:<br>
<input type="radio" name="colorscheme" id="sequential" value="seq"><label for="sequential">sequential</label>
<input type="radio" name="colorscheme" id="diverting" value="div"><label for="diverting">diverting</label>
<input type="radio" name="colorscheme" id="qualitative" value="qual" checked><label for="qualitative">qualitative</label><br>
<input type="checkbox" name="colorsreversed" id="colorsreversed"><label for="colorsreversed">reverse color order</label><br>
<input type="checkbox" id="hidenulls" name="hidenulls" checked><label for="hidenulls">Hide no-data</label><br>
<div id="colorschemes"></div>
</div>
</div>
<div id="featureinfo"></div>
</div>
</div>
<div id="layerjson"></div>
<div id="back"></div>
</body>
</html>
\ No newline at end of file
// This product includes color specifications and designs developed by Cynthia Brewer (http://colorbrewer.org/).
// JavaScript specs as packaged in the D3 library (d3js.org). Please see license at http://colorbrewer.org/export/LICENSE.txt
// --
// generator described at end of file
const colorbrewer = [
{
"name": "Spectral",
"sets": [
{
"colors": [
"#fc8d59",
"#ffffbf",
"#99d594"
],
"blind": "maybe",
"print": "ok",
"screen": "ok",
"copy": "ok"
},
{
"colors": [
"#d7191c",
"#fdae61",
"#abdda4",
"#2b83ba"
],
"blind": "maybe",
"print": "ok",
"screen": "ok",
"copy": "ok"
},
{
"colors": [
"#d7191c",
"#fdae61",
"#ffffbf",
"#abdda4",
"#2b83ba"
],
"blind": "maybe",
"print": "ok",
"screen": "maybe",
"copy": "ok"
},
{
"colors": [
"#d53e4f",
"#fc8d59",
"#fee08b",
"#e6f598",
"#99d594",
"#3288bd"
],
"blind": "bad",
"print": "bad",
"screen": "bad",
"copy": "bad"
},
{
"colors": [
"#d53e4f",
"#fc8d59",
"#fee08b",
"#ffffbf",
"#e6f598",
"#99d594",
"#3288bd"
],
"blind": "bad",
"print": "bad",
"screen": "bad",
"copy": "bad"
},
{
"colors": [
"#d53e4f",
"#f46d43",
"#fdae61",
"#fee08b",
"#e6f598",
"#abdda4",
"#66c2a5",
"#3288bd"
],
"blind": "bad",
"print": "bad",
"screen": "bad",
"copy": "bad"
},
{
"colors": [
"#d53e4f",
"#f46d43",
"#fdae61",
"#fee08b",
"#ffffbf",
"#e6f598",
"#abdda4",
"#66c2a5",
"#3288bd"
],
"blind": "bad",
"print": "bad",
"screen": "bad",
"copy": "bad"
},
{
"colors": [
"#9e0142",
"#d53e4f",
"#f46d43",
"#fdae61",
"#fee08b",
"#e6f598",
"#abdda4",
"#66c2a5",
"#3288bd",
"#5e4fa2"
],
"blind": "bad",
"print": "bad",
"screen": "bad",
"copy": "bad"
},
{
"colors": [
"#9e0142",
"#d53e4f",
"#f46d43",
"#fdae61",
"#fee08b",
"#ffffbf",
"#e6f598",
"#abdda4",
"#66c2a5",
"#3288bd",
"#5e4fa2"
],
"blind": "bad",
"print": "bad",
"screen": "bad",
"copy": "bad"
}
],
"type": "div"
},
{
"name": "RdYlGn",
"sets": [
{
"colors": [
"#fc8d59",
"#ffffbf",
"#91cf60"
],
"blind": "maybe",
"print": "ok",
"screen": "ok",
"copy": "bad"
},
{
"colors": [
"#d7191c",
"#fdae61",
"#a6d96a",
"#1a9641"
],
"blind": "maybe",
"print": "ok",
"screen": "ok",
"copy": "bad"
},
{
"colors": [
"#d7191c",
"#fdae61",
"#ffffbf",
"#a6d96a",
"#1a9641"
],
"blind": "maybe",
"print": "ok",
"screen": "ok",
"copy": "bad"
},
{
"colors": [
"#d73027",
"#fc8d59",
"#fee08b",
"#d9ef8b",
"#91cf60",
"#1a9850"
],
"blind": "bad",
"print": "maybe",
"screen": "bad",
"copy": "bad"
},
{
"colors": [
"#d73027",
"#fc8d59",
"#fee08b",
"#ffffbf",
"#d9ef8b",
"#91cf60",
"#1a9850"
],
"blind": "bad",
"print": "bad",
"screen": "bad",
"copy": "bad"
},
{
"colors": [
"#d73027",
"#f46d43",
"#fdae61",
"#fee08b",
"#d9ef8b",
"#a6d96a",
"#66bd63",
"#1a9850"
],
"blind": "bad",
"print": "bad",
"screen": "bad",
"copy": "bad"
},
{
"colors": [
"#d73027",
"#f46d43",
"#fdae61",
"#fee08b",
"#ffffbf",
"#d9ef8b",
"#a6d96a",
"#66bd63",
"#1a9850"
],
"blind": "bad",
"print": "bad",
"screen": "bad",
"copy": "bad"
},
{
"colors": [
"#a50026",
"#d73027",
"#f46d43",
"#fdae61",
"#fee08b",
"#d9ef8b",
"#a6d96a",
"#66bd63",
"#1a9850",
"#006837"
],
"blind": "bad",
"print": "bad",
"screen": "bad",
"copy": "bad"
},
{
"colors": [
"#a50026",
"#d73027",
"#f46d43",
"#fdae61",
"#fee08b",
"#ffffbf",
"#d9ef8b",
"#a6d96a",
"#66bd63",
"#1a9850",
"#006837"
],
"blind": "bad",
"print": "bad",
"screen": "bad",
"copy": "bad"
}
],
"type": "div"
},
{
"name": "RdBu",
"sets": [
{
"colors": [
"#ef8a62",
"#f7f7f7",
"#67a9cf"
],
"blind": "ok",
"print": "ok",
"screen": "ok",
"copy": "bad"
},
{
"colors": [
"#ca0020",
"#f4a582",
"#92c5de",
"#0571b0"
],
"blind": "ok",
"print": "ok",
"screen": "ok",
"copy": "bad"
},
{
"colors": [
"#ca0020",
"#f4a582",
"#f7f7f7",
"#92c5de",
"#0571b0"
],
"blind": "ok",
"print": "ok",
"screen": "ok",
"copy": "bad"
},
{
"colors": [
"#b2182b",
"#ef8a62",
"#fddbc7",
"#d1e5f0",
"#67a9cf",
"#2166ac"
],
"blind": "ok",
"print": "ok",
"screen": "bad",
"copy": "bad"
},
{
"colors": [
"#b2182b",
"#ef8a62",
"#fddbc7",
"#f7f7f7",
"#d1e5f0",
"#67a9cf",
"#2166ac"
],
"blind": "ok",
"print": "bad",
"screen": "bad",
"copy": "bad"
},
{
"colors": [
"#b2182b",
"#d6604d",
"#f4a582",
"#fddbc7",
"#d1e5f0",
"#92c5de",
"#4393c3",
"#2166ac"
],
"blind": "ok",
"print": "bad",
"screen": "bad",
"copy": "bad"
},
{
"colors": [
"#b2182b",
"#d6604d",
"#f4a582",
"#fddbc7",
"#f7f7f7",
"#d1e5f0",
"#92c5de",
"#4393c3",
"#2166ac"
],
"blind": "ok",
"print": "bad",
"screen": "bad",
"copy": "bad"
},
{
"colors": [
"#67001f",
"#b2182b",
"#d6604d",
"#f4a582",
"#fddbc7",
"#d1e5f0",
"#92c5de",
"#4393c3",
"#2166ac",
"#053061"
],
"blind": "ok",
"print": "bad",
"screen": "bad",
"copy": "bad"
},
{
"colors": [
"#67001f",
"#b2182b",
"#d6604d",
"#f4a582",
"#fddbc7",
"#f7f7f7",
"#d1e5f0",
"#92c5de",
"#4393c3",
"#2166ac",
"#053061"
],
"blind": "ok",
"print": "bad",
"screen": "bad",
"copy": "bad"
}
],
"type": "div"
},
{
"name": "PiYG",
"sets": [
{
"colors": [
"#e9a3c9",
"#f7f7f7",
"#a1d76a"
],
"blind": "ok",
"print": "ok",
"screen": "ok",
"copy": "bad"
},
{
"colors": [
"#d01c8b",
"#f1b6da",
"#b8e186",
"#4dac26"
],
"blind": "ok",
"print": "ok",
"screen": "ok",
"copy": "bad"
},
{
"colors": [
"#d01c8b",
"#f1b6da",
"#f7f7f7",
"#b8e186",
"#4dac26"
],
"blind": "ok",
"print": "maybe",
"screen": "maybe",
"copy": "bad"
},
{
"colors": [
"#c51b7d",
"#e9a3c9",
"#fde0ef",
"#e6f5d0",
"#a1d76a",
"#4d9221"
],
"blind": "ok",
"print": "bad",
"screen": "bad",
"copy": "bad"
},
{
"colors": [
"#c51b7d",
"#e9a3c9",
"#fde0ef",
"#f7f7f7",
"#e6f5d0",
"#a1d76a",
"#4d9221"
],
"blind": "ok",
"print": "bad",
"screen": "bad",
"copy": "bad"
},
{
"colors": [
"#c51b7d",
"#de77ae",
"#f1b6da",
"#fde0ef",
"#e6f5d0",
"#b8e186",
"#7fbc41",
"#4d9221"
],
"blind": "ok",
"print": "bad",
"screen": "bad",
"copy": "bad"
},
{
"colors": [
"#c51b7d",
"#de77ae",
"#f1b6da",
"#fde0ef",
"#f7f7f7",
"#e6f5d0",
"#b8e186",
"#7fbc41",
"#4d9221"
],
"blind": "ok",
"print": "bad",
"screen": "bad",
"copy": "bad"
},
{
"colors": [
"#8e0152",
"#c51b7d",
"#de77ae",
"#f1b6da",
"#fde0ef",
"#e6f5d0",
"#b8e186",
"#7fbc41",
"#4d9221",
"#276419"
],
"blind": "ok",
"print": "bad",
"screen": "bad",
"copy": "bad"
},
{
"colors": [
"#8e0152",
"#c51b7d",
"#de77ae",
"#f1b6da",
"#fde0ef",
"#f7f7f7",
"#e6f5d0",
"#b8e186",
"#7fbc41",
"#4d9221",
"#276419"
],
"blind": "ok",
"print": "bad",
"screen": "bad",
"copy": "bad"
}
],
"type": "div"
},
{
"name": "PRGn",
"sets": [
{
"colors": [
"#af8dc3",
"#f7f7f7",
"#7fbf7b"
],
"blind": "ok",
"print": "ok",
"screen": "ok",
"copy": "bad"
},
{
"colors": [
"#7b3294",
"#c2a5cf",
"#a6dba0",
"#008837"
],
"blind": "ok",
"print": "ok",
"screen": "ok",
"copy": "bad"
},
{
"colors": [
"#7b3294",
"#c2a5cf",
"#f7f7f7",
"#a6dba0",
"#008837"
],
"blind": "ok",
"print": "ok",
"screen": "maybe",
"copy": "bad"
},
{
"colors": [
"#762a83",
"#af8dc3",
"#e7d4e8",
"#d9f0d3",
"#7fbf7b",
"#1b7837"
],
"blind": "ok",
"print": "ok",
"screen": "maybe",
"copy": "bad"
},
{
"colors": [
"#762a83",
"#af8dc3",
"#e7d4e8",
"#f7f7f7",
"#d9f0d3",
"#7fbf7b",
"#1b7837"
],
"blind": "ok",
"print": "bad",
"screen": "bad",
"copy": "bad"
},
{
"colors": [
"#762a83",
"#9970ab",
"#c2a5cf",
"#e7d4e8",
"#d9f0d3",
"#a6dba0",
"#5aae61",
"#1b7837"
],
"blind": "ok",
"print": "bad",
"screen": "bad",
"copy": "bad"
},
{
"colors": [
"#762a83",
"#9970ab",
"#c2a5cf",
"#e7d4e8",
"#f7f7f7",
"#d9f0d3",
"#a6dba0",
"#5aae61",
"#1b7837"
],
"blind": "ok",
"print": "bad",
"screen": "bad",
"copy": "bad"
},
{
"colors": [
"#40004b",
"#762a83",
"#9970ab",
"#c2a5cf",
"#e7d4e8",
"#d9f0d3",
"#a6dba0",
"#5aae61",
"#1b7837",
"#00441b"
],
"blind": "ok",
"print": "bad",
"screen": "bad",
"copy": "bad"
},
{
"colors": [
"#40004b",
"#762a83",
"#9970ab",
"#c2a5cf",
"#e7d4e8",
"#f7f7f7",
"#d9f0d3",
"#a6dba0",
"#5aae61",
"#1b7837",
"#00441b"
],
"blind": "ok",
"print": "bad",
"screen": "bad",
"copy": "bad"
}
],
"type": "div"
},
{
"name": "RdYlBu",
"sets": [
{
"colors": [
"#fc8d59",
"#ffffbf",
"#91bfdb"
],
"blind": "ok",
"print": "ok",
"screen": "ok",
"copy": "bad"
},
{
"colors": [
"#d7191c",
"#fdae61",
"#abd9e9",
"#2c7bb6"
],
"blind": "ok",
"print": "ok",
"screen": "ok",
"copy": "bad"
},
{
"colors": [
"#d7191c",
"#fdae61",
"#ffffbf",
"#abd9e9",
"#2c7bb6"
],
"blind": "ok",
"print": "ok",
"screen": "ok",
"copy": "bad"
},
{
"colors": [
"#d73027",
"#fc8d59",
"#fee090",
"#e0f3f8",
"#91bfdb",
"#4575b4"
],
"blind": "ok",
"print": "ok",
"screen": "maybe",
"copy": "bad"
},
{
"colors": [
"#d73027",
"#fc8d59",
"#fee090",
"#ffffbf",
"#e0f3f8",
"#91bfdb",
"#4575b4"
],
"blind": "ok",
"print": "maybe",
"screen": "bad",
"copy": "bad"
},
{
"colors": [
"#d73027",
"#f46d43",
"#fdae61",
"#fee090",
"#e0f3f8",
"#abd9e9",
"#74add1",
"#4575b4"
],
"blind": "ok",
"print": "bad",
"screen": "bad",
"copy": "bad"
},
{
"colors": [
"#d73027",
"#f46d43",
"#fdae61",
"#fee090",
"#ffffbf",
"#e0f3f8",
"#abd9e9",
"#74add1",
"#4575b4"
],
"blind": "ok",
"print": "bad",
"screen": "bad",
"copy": "bad"
},
{
"colors": [
"#a50026",
"#d73027",
"#f46d43",
"#fdae61",
"#fee090",
"#e0f3f8",
"#abd9e9",
"#74add1",
"#4575b4",
"#313695"
],
"blind": "ok",
"print": "bad",
"screen": "bad",
"copy": "bad"
},
{
"colors": [
"#a50026",
"#d73027",
"#f46d43",
"#fdae61",
"#fee090",
"#ffffbf",
"#e0f3f8",
"#abd9e9",
"#74add1",
"#4575b4",
"#313695"
],
"blind": "ok",
"print": "bad",
"screen": "bad",
"copy": "bad"
}
],
"type": "div"
},
{
"name": "BrBG",
"sets": [
{
"colors": [
"#d8b365",
"#f5f5f5",
"#5ab4ac"
],
"blind": "ok",
"print": "ok",
"screen": "ok",
"copy": "bad"
},
{
"colors": [
"#a6611a",
"#dfc27d",
"#80cdc1",
"#018571"
],
"blind": "ok",
"print": "ok",
"screen": "ok",
"copy": "bad"
},
{
"colors": [
"#a6611a",
"#dfc27d",
"#f5f5f5",
"#80cdc1",
"#018571"
],
"blind": "ok",
"print": "ok",
"screen": "ok",
"copy": "bad"
},
{
"colors": [
"#8c510a",
"#d8b365",
"#f6e8c3",
"#c7eae5",
"#5ab4ac",
"#01665e"
],
"blind": "ok",
"print": "ok",
"screen": "ok",
"copy": "bad"
},
{
"colors": [
"#8c510a",
"#d8b365",
"#f6e8c3",
"#f5f5f5",
"#c7eae5",
"#5ab4ac",
"#01665e"
],
"blind": "ok",
"print": "bad",
"screen": "bad",
"copy": "bad"
},
{
"colors": [
"#8c510a",
"#bf812d",
"#dfc27d",
"#f6e8c3",
"#c7eae5",
"#80cdc1",
"#35978f",
"#01665e"
],
"blind": "ok",
"print": "bad",
"screen": "bad",
"copy": "bad"
},
{
"colors": [
"#8c510a",
"#bf812d",
"#dfc27d",
"#f6e8c3",
"#f5f5f5",
"#c7eae5",
"#80cdc1",
"#35978f",
"#01665e"
],
"blind": "ok",
"print": "bad",
"screen": "bad",
"copy": "bad"
},
{
"colors": [
"#543005",
"#8c510a",
"#bf812d",
"#dfc27d",
"#f6e8c3",
"#c7eae5",
"#80cdc1",
"#35978f",
"#01665e",
"#003c30"
],
"blind": "ok",
"print": "bad",
"screen": "bad",
"copy": "bad"
},
{
"colors": [
"#543005",
"#8c510a",
"#bf812d",
"#dfc27d",
"#f6e8c3",
"#f5f5f5",
"#c7eae5",
"#80cdc1",
"#35978f",
"#01665e",
"#003c30"
],
"blind": "ok",
"print": "bad",
"screen": "bad",
"copy": "bad"
}
],
"type": "div"
},
{
"name": "RdGy",
"sets": [
{
"colors": [
"#ef8a62",
"#ffffff",
"#999999"
],
"blind": "maybe",
"print": "ok",
"screen": "ok",
"copy": "bad"
},
{
"colors": [
"#ca0020",
"#f4a582",
"#bababa",
"#404040"
],
"blind": "maybe",
"print": "ok",
"screen": "ok",
"copy": "bad"
},
{
"colors": [
"#ca0020",
"#f4a582",
"#ffffff",
"#bababa",
"#404040"
],
"blind": "maybe",
"print": "ok",
"screen": "maybe",
"copy": "bad"
},
{
"colors": [
"#b2182b",
"#ef8a62",
"#fddbc7",
"#e0e0e0",
"#999999",
"#4d4d4d"
],
"blind": "maybe",
"print": "maybe",
"screen": "bad",
"copy": "bad"
},
{
"colors": [
"#b2182b",
"#ef8a62",
"#fddbc7",
"#ffffff",
"#e0e0e0",
"#999999",
"#4d4d4d"
],
"blind": "maybe",
"print": "bad",
"screen": "bad",
"copy": "bad"
},
{
"colors": [
"#b2182b",
"#d6604d",
"#f4a582",
"#fddbc7",
"#e0e0e0",
"#bababa",
"#878787",
"#4d4d4d"
],
"blind": "maybe",
"print": "bad",
"screen": "bad",
"copy": "bad"
},
{
"colors": [
"#b2182b",
"#d6604d",
"#f4a582",
"#fddbc7",
"#ffffff",
"#e0e0e0",
"#bababa",
"#878787",
"#4d4d4d"
],
"blind": "maybe",
"print": "bad",
"screen": "bad",
"copy": "bad"
},
{
"colors": [
"#67001f",
"#b2182b",
"#d6604d",
"#f4a582",
"#fddbc7",
"#e0e0e0",
"#bababa",
"#878787",
"#4d4d4d",
"#1a1a1a"
],
"blind": "maybe",
"print": "bad",
"screen": "bad",
"copy": "bad"
},
{
"colors": [
"#67001f",
"#b2182b",
"#d6604d",
"#f4a582",
"#fddbc7",
"#ffffff",
"#e0e0e0",
"#bababa",
"#878787",
"#4d4d4d",
"#1a1a1a"
],
"blind": "maybe",
"print": "bad",
"screen": "bad",
"copy": "bad"
}
],
"type": "div"
},
{
"name": "PuOr",
"sets": [
{
"colors": [
"#f1a340",
"#f7f7f7",
"#998ec3"
],
"blind": "ok",
"print": "ok",
"screen": "ok",
"copy": "ok"
},
{
"colors": [
"#e66101",
"#fdb863",
"#b2abd2",
"#5e3c99"
],
"blind": "ok",
"print": "ok",
"screen": "ok",
"copy": "ok"
},
{
"colors": [
"#e66101",
"#fdb863",
"#f7f7f7",
"#b2abd2",
"#5e3c99"
],
"blind": "ok",
"print": "maybe",
"screen": "ok",
"copy": "bad"
},
{
"colors": [
"#b35806",
"#f1a340",
"#fee0b6",
"#d8daeb",
"#998ec3",
"#542788"
],
"blind": "ok",
"print": "maybe",
"screen": "ok",
"copy": "bad"
},
{
"colors": [
"#b35806",
"#f1a340",
"#fee0b6",
"#f7f7f7",
"#d8daeb",
"#998ec3",
"#542788"
],
"blind": "ok",
"print": "bad",
"screen": "bad",
"copy": "bad"
},
{
"colors": [
"#b35806",
"#e08214",
"#fdb863",
"#fee0b6",
"#d8daeb",
"#b2abd2",
"#8073ac",
"#542788"
],
"blind": "ok",
"print": "bad",
"screen": "bad",
"copy": "bad"
},
{
"colors": [
"#b35806",
"#e08214",
"#fdb863",
"#fee0b6",
"#f7f7f7",
"#d8daeb",
"#b2abd2",
"#8073ac",
"#542788"
],
"blind": "ok",
"print": "bad",
"screen": "bad",
"copy": "bad"
},
{
"colors": [
"#7f3b08",
"#b35806",
"#e08214",
"#fdb863",
"#fee0b6",
"#d8daeb",
"#b2abd2",
"#8073ac",
"#542788",
"#2d004b"
],
"blind": "ok",
"print": "bad",
"screen": "bad",
"copy": "bad"
},
{
"colors": [
"#7f3b08",
"#b35806",
"#e08214",
"#fdb863",
"#fee0b6",
"#f7f7f7",
"#d8daeb",
"#b2abd2",
"#8073ac",
"#542788",
"#2d004b"
],
"blind": "ok",
"print": "bad",
"screen": "bad",
"copy": "bad"
}
],
"type": "div"
},
{
"name": "Set2",
"sets": [
{
"colors": [
"#66c2a5",
"#fc8d62",
"#8da0cb"
],
"blind": "ok",
"print": "ok",
"screen": "ok",
"copy": "bad"
},
{
"colors": [
"#66c2a5",
"#fc8d62",
"#8da0cb",
"#e78ac3"
],
"blind": "maybe",
"print": "ok",
"screen": "ok",
"copy": "bad"
},
{
"colors": [
"#66c2a5",
"#fc8d62",
"#8da0cb",
"#e78ac3",
"#a6d854"
],
"blind": "maybe",
"print": "ok",
"screen": "maybe",
"copy": "bad"
},
{
"colors": [
"#66c2a5",
"#fc8d62",
"#8da0cb",
"#e78ac3",
"#a6d854",
"#ffd92f"
],
"blind": "maybe",
"print": "maybe",
"screen": "maybe",
"copy": "bad"
},
{
"colors": [
"#66c2a5",
"#fc8d62",
"#8da0cb",
"#e78ac3",
"#a6d854",
"#ffd92f",
"#e5c494"
],
"blind": "bad",
"print": "maybe",
"screen": "maybe",
"copy": "bad"
},
{
"colors": [
"#66c2a5",
"#fc8d62",
"#8da0cb",
"#e78ac3",
"#a6d854",
"#ffd92f",
"#e5c494",
"#b3b3b3"
],
"blind": "bad",
"print": "maybe",
"screen": "maybe",
"copy": "bad"
}
],
"type": "qual"
},
{
"name": "Accent",
"sets": [
{
"colors": [
"#7fc97f",
"#beaed4",
"#fdc086"
],
"blind": "maybe",
"print": "ok",
"screen": "ok",
"copy": "bad"
},
{
"colors": [
"#7fc97f",
"#beaed4",
"#fdc086",
"#ffff99"
],
"blind": "bad",
"print": "ok",
"screen": "ok",
"copy": "bad"
},
{
"colors": [
"#7fc97f",
"#beaed4",
"#fdc086",
"#ffff99",
"#386cb0"
],
"blind": "bad",
"print": "maybe",
"screen": "ok",
"copy": "bad"
},
{
"colors": [
"#7fc97f",
"#beaed4",
"#fdc086",
"#ffff99",
"#386cb0",
"#f0027f"
],
"blind": "bad",
"print": "maybe",
"screen": "maybe",
"copy": "bad"
},
{
"colors": [
"#7fc97f",
"#beaed4",
"#fdc086",
"#ffff99",
"#386cb0",
"#f0027f",
"#bf5b17"
],
"blind": "bad",
"print": "maybe",
"screen": "maybe",
"copy": "bad"
},
{
"colors": [
"#7fc97f",
"#beaed4",
"#fdc086",
"#ffff99",
"#386cb0",
"#f0027f",
"#bf5b17",
"#666666"
],
"blind": "bad",
"print": "maybe",
"screen": "maybe",
"copy": "bad"
}
],
"type": "qual"
},
{
"name": "Set1",
"sets": [
{
"colors": [
"#e41a1c",
"#377eb8",
"#4daf4a"
],
"blind": "maybe",
"print": "ok",
"screen": "ok",
"copy": "bad"
},
{
"colors": [
"#e41a1c",
"#377eb8",
"#4daf4a",
"#984ea3"
],
"blind": "maybe",
"print": "ok",
"screen": "ok",
"copy": "bad"
},
{
"colors": [
"#e41a1c",
"#377eb8",
"#4daf4a",
"#984ea3",
"#ff7f00"
],
"blind": "maybe",
"print": "ok",
"screen": "ok",
"copy": "bad"
},
{
"colors": [
"#e41a1c",
"#377eb8",
"#4daf4a",
"#984ea3",
"#ff7f00",
"#ffff33"
],
"blind": "maybe",
"print": "ok",
"screen": "ok",
"copy": "bad"
},
{
"colors": [
"#e41a1c",
"#377eb8",
"#4daf4a",
"#984ea3",
"#ff7f00",
"#ffff33",
"#a65628"
],
"blind": "maybe",
"print": "ok",
"screen": "ok",
"copy": "bad"
},
{
"colors": [
"#e41a1c",
"#377eb8",
"#4daf4a",
"#984ea3",
"#ff7f00",
"#ffff33",
"#a65628",
"#f781bf"
],
"blind": "maybe",
"print": "ok",
"screen": "ok",
"copy": "bad"
},
{
"colors": [
"#e41a1c",
"#377eb8",
"#4daf4a",
"#984ea3",
"#ff7f00",
"#ffff33",
"#a65628",
"#f781bf",
"#999999"
],
"blind": "maybe",
"print": "ok",
"screen": "ok",
"copy": "bad"
}
],
"type": "qual"
},
{
"name": "Set3",
"sets": [
{
"colors": [
"#8dd3c7",
"#ffffb3",
"#bebada"
],
"blind": "maybe",
"print": "ok",
"screen": "ok",
"copy": "ok"
},
{
"colors": [
"#8dd3c7",
"#ffffb3",
"#bebada",
"#fb8072"
],
"blind": "maybe",
"print": "ok",
"screen": "ok",
"copy": "maybe"
},
{
"colors": [
"#8dd3c7",
"#ffffb3",
"#bebada",
"#fb8072",
"#80b1d3"
],
"blind": "bad",
"print": "ok",
"screen": "ok",
"copy": "maybe"
},
{
"colors": [
"#8dd3c7",
"#ffffb3",
"#bebada",
"#fb8072",
"#80b1d3",
"#fdb462"
],
"blind": "bad",
"print": "ok",
"screen": "maybe",
"copy": "maybe"
},
{
"colors": [
"#8dd3c7",
"#ffffb3",
"#bebada",
"#fb8072",
"#80b1d3",
"#fdb462",
"#b3de69"
],
"blind": "bad",
"print": "ok",
"screen": "maybe",
"copy": "maybe"
},
{
"colors": [
"#8dd3c7",
"#ffffb3",
"#bebada",
"#fb8072",
"#80b1d3",
"#fdb462",
"#b3de69",
"#fccde5"
],
"blind": "bad",
"print": "ok",
"screen": "maybe",
"copy": "maybe"
},
{
"colors": [
"#8dd3c7",
"#ffffb3",
"#bebada",
"#fb8072",
"#80b1d3",
"#fdb462",
"#b3de69",
"#fccde5",
"#d9d9d9"
],
"blind": "bad",
"print": "maybe",
"screen": "bad",
"copy": "maybe"
},
{
"colors": [
"#8dd3c7",
"#ffffb3",
"#bebada",
"#fb8072",
"#80b1d3",
"#fdb462",
"#b3de69",
"#fccde5",
"#d9d9d9",
"#bc80bd"
],
"blind": "bad",
"print": "bad",
"screen": "bad",
"copy": "bad"
},
{
"colors": [
"#8dd3c7",
"#ffffb3",
"#bebada",
"#fb8072",
"#80b1d3",
"#fdb462",
"#b3de69",
"#fccde5",
"#d9d9d9",
"#bc80bd",
"#ccebc5"
],
"blind": "bad",
"print": "bad",
"screen": "bad",
"copy": "bad"
},
{
"colors": [
"#8dd3c7",
"#ffffb3",
"#bebada",
"#fb8072",
"#80b1d3",
"#fdb462",
"#b3de69",
"#fccde5",
"#d9d9d9",
"#bc80bd",
"#ccebc5",
"#ffed6f"
],
"blind": "bad",
"print": "bad",
"screen": "bad",
"copy": "bad"
}
],
"type": "qual"
},
{
"name": "Dark2",
"sets": [
{
"colors": [
"#1b9e77",
"#d95f02",
"#7570b3"
],
"blind": "ok",
"print": "ok",
"screen": "ok",
"copy": "bad"
},
{
"colors": [
"#1b9e77",
"#d95f02",
"#7570b3",
"#e7298a"
],
"blind": "maybe",
"print": "ok",
"screen": "ok",
"copy": "bad"
},
{
"colors": [
"#1b9e77",
"#d95f02",
"#7570b3",
"#e7298a",
"#66a61e"
],
"blind": "maybe",
"print": "ok",
"screen": "ok",
"copy": "bad"
},
{
"colors": [
"#1b9e77",
"#d95f02",
"#7570b3",
"#e7298a",
"#66a61e",
"#e6ab02"
],
"blind": "maybe",
"print": "ok",
"screen": "ok",
"copy": "bad"
},
{
"colors": [
"#1b9e77",
"#d95f02",
"#7570b3",
"#e7298a",
"#66a61e",
"#e6ab02",
"#a6761d"
],
"blind": "bad",
"print": "ok",
"screen": "ok",
"copy": "bad"
},
{
"colors": [
"#1b9e77",
"#d95f02",
"#7570b3",
"#e7298a",
"#66a61e",
"#e6ab02",
"#a6761d",
"#666666"
],
"blind": "bad",
"print": "ok",
"screen": "ok",
"copy": "bad"
}
],
"type": "qual"
},
{
"name": "Paired",
"sets": [
{
"colors": [
"#a6cee3",
"#1f78b4",
"#b2df8a"
],
"blind": "ok",
"print": "ok",
"screen": "ok",
"copy": "bad"
},
{
"colors": [
"#a6cee3",
"#1f78b4",
"#b2df8a",
"#33a02c"
],
"blind": "ok",
"print": "ok",
"screen": "ok",
"copy": "bad"
},
{
"colors": [
"#a6cee3",
"#1f78b4",
"#b2df8a",
"#33a02c",
"#fb9a99"
],
"blind": "maybe",
"print": "ok",
"screen": "ok",
"copy": "bad"
},
{
"colors": [
"#a6cee3",
"#1f78b4",
"#b2df8a",
"#33a02c",
"#fb9a99",
"#e31a1c"
],
"blind": "maybe",
"print": "ok",
"screen": "ok",
"copy": "bad"
},
{
"colors": [
"#a6cee3",
"#1f78b4",
"#b2df8a",
"#33a02c",
"#fb9a99",
"#e31a1c",
"#fdbf6f"
],
"blind": "maybe",
"print": "ok",
"screen": "ok",
"copy": "bad"
},
{
"colors": [
"#a6cee3",
"#1f78b4",
"#b2df8a",
"#33a02c",
"#fb9a99",
"#e31a1c",
"#fdbf6f",
"#ff7f00"
],
"blind": "maybe",
"print": "maybe",
"screen": "ok",
"copy": "bad"
},
{
"colors": [
"#a6cee3",
"#1f78b4",
"#b2df8a",
"#33a02c",
"#fb9a99",
"#e31a1c",
"#fdbf6f",
"#ff7f00",
"#cab2d6"
],
"blind": "bad",
"print": "maybe",
"screen": "ok",
"copy": "bad"
},
{
"colors": [
"#a6cee3",
"#1f78b4",
"#b2df8a",
"#33a02c",
"#fb9a99",
"#e31a1c",
"#fdbf6f",
"#ff7f00",
"#cab2d6",
"#6a3d9a"
],
"blind": "bad",
"print": "maybe",
"screen": "ok",
"copy": "bad"
},
{
"colors": [
"#a6cee3",
"#1f78b4",
"#b2df8a",
"#33a02c",
"#fb9a99",
"#e31a1c",
"#fdbf6f",
"#ff7f00",
"#cab2d6",
"#6a3d9a",
"#ffff99"
],
"blind": "bad",
"print": "maybe",
"screen": "maybe",
"copy": "bad"
},
{
"colors": [
"#a6cee3",
"#1f78b4",
"#b2df8a",
"#33a02c",
"#fb9a99",
"#e31a1c",
"#fdbf6f",
"#ff7f00",
"#cab2d6",
"#6a3d9a",
"#ffff99",
"#b15928"
],
"copy": "bad"
}
],
"type": "qual"
},
{
"name": "Pastel2",
"sets": [
{
"colors": [
"#b3e2cd",
"#fdcdac",
"#cbd5e8"
],
"blind": "maybe",
"print": "maybe",
"screen": "maybe",
"copy": "bad"
},
{
"colors": [
"#b3e2cd",
"#fdcdac",
"#cbd5e8",
"#f4cae4"
],
"blind": "bad",
"print": "bad",
"screen": "maybe",
"copy": "bad"
},
{
"colors": [
"#b3e2cd",
"#fdcdac",
"#cbd5e8",
"#f4cae4",
"#e6f5c9"
],
"blind": "bad",
"print": "bad",
"screen": "bad",
"copy": "bad"
},
{
"colors": [
"#b3e2cd",
"#fdcdac",
"#cbd5e8",
"#f4cae4",
"#e6f5c9",
"#fff2ae"
],
"blind": "bad",
"print": "bad",
"screen": "bad",
"copy": "bad"
},
{
"colors": [
"#b3e2cd",
"#fdcdac",
"#cbd5e8",
"#f4cae4",
"#e6f5c9",
"#fff2ae",
"#f1e2cc"
],
"blind": "bad",
"print": "bad",
"screen": "bad",
"copy": "bad"
},
{
"colors": [
"#b3e2cd",
"#fdcdac",
"#cbd5e8",
"#f4cae4",
"#e6f5c9",
"#fff2ae",
"#f1e2cc",
"#cccccc"
],
"blind": "bad",
"print": "bad",
"screen": "bad",
"copy": "bad"
}
],
"type": "qual"
},
{
"name": "Pastel1",
"sets": [
{
"colors": [
"#fbb4ae",
"#b3cde3",
"#ccebc5"
],
"blind": "maybe",
"print": "maybe",
"screen": "maybe",
"copy": "bad"
},
{
"colors": [
"#fbb4ae",
"#b3cde3",
"#ccebc5",
"#decbe4"
],
"blind": "bad",
"print": "maybe",
"screen": "maybe",
"copy": "bad"
},
{
"colors": [
"#fbb4ae",
"#b3cde3",
"#ccebc5",
"#decbe4",
"#fed9a6"
],
"blind": "bad",
"print": "maybe",
"screen": "maybe",
"copy": "bad"
},
{
"colors": [
"#fbb4ae",
"#b3cde3",
"#ccebc5",
"#decbe4",
"#fed9a6",
"#ffffcc"
],
"blind": "bad",
"print": "bad",
"screen": "maybe",
"copy": "bad"
},
{
"colors": [
"#fbb4ae",
"#b3cde3",
"#ccebc5",
"#decbe4",
"#fed9a6",
"#ffffcc",
"#e5d8bd"
],
"blind": "bad",
"print": "bad",
"screen": "bad",
"copy": "bad"
},
{
"colors": [
"#fbb4ae",
"#b3cde3",
"#ccebc5",
"#decbe4",
"#fed9a6",
"#ffffcc",
"#e5d8bd",
"#fddaec"
],
"blind": "bad",
"print": "bad",
"screen": "bad",
"copy": "bad"
},
{
"colors": [
"#fbb4ae",
"#b3cde3",
"#ccebc5",
"#decbe4",
"#fed9a6",
"#ffffcc",
"#e5d8bd",
"#fddaec",
"#f2f2f2"
],
"blind": "bad",
"print": "bad",
"screen": "bad",
"copy": "bad"
}
],
"type": "qual"
},
{
"name": "OrRd",
"sets": [
{
"colors": [
"#fee8c8",
"#fdbb84",
"#e34a33"
],
"blind": "ok",
"print": "ok",
"screen": "ok",
"copy": "ok"
},
{
"colors": [
"#fef0d9",
"#fdcc8a",
"#fc8d59",
"#d7301f"
],
"blind": "ok",
"print": "ok",
"screen": "ok",
"copy": "ok"
},
{
"colors": [
"#fef0d9",
"#fdcc8a",
"#fc8d59",
"#e34a33",
"#b30000"
],
"blind": "ok",
"print": "bad",
"screen": "ok",
"copy": "maybe"
},
{
"colors": [
"#fef0d9",
"#fdd49e",
"#fdbb84",
"#fc8d59",
"#e34a33",
"#b30000"
],
"blind": "ok",
"print": "bad",
"screen": "bad",
"copy": "bad"
},
{
"colors": [
"#fef0d9",
"#fdd49e",
"#fdbb84",
"#fc8d59",
"#ef6548",
"#d7301f",
"#990000"
],
"blind": "ok",
"print": "bad",
"screen": "bad",
"copy": "bad"
},
{
"colors": [
"#fff7ec",
"#fee8c8",
"#fdd49e",
"#fdbb84",
"#fc8d59",
"#ef6548",
"#d7301f",
"#990000"
],
"blind": "ok",
"print": "bad",
"screen": "bad",
"copy": "bad"
},
{
"colors": [
"#fff7ec",
"#fee8c8",
"#fdd49e",
"#fdbb84",
"#fc8d59",
"#ef6548",
"#d7301f",
"#b30000",
"#7f0000"
],
"blind": "ok",
"print": "bad",
"screen": "bad",
"copy": "bad"
}
],
"type": "seq"
},
{
"name": "PuBu",
"sets": [
{
"colors": [
"#ece7f2",
"#a6bddb",
"#2b8cbe"
],
"blind": "ok",
"print": "ok",
"screen": "ok",
"copy": "ok"
},
{
"colors": [
"#f1eef6",
"#bdc9e1",
"#74a9cf",
"#0570b0"
],
"blind": "ok",
"print": "maybe",
"screen": "ok",
"copy": "maybe"
},
{
"colors": [
"#f1eef6",
"#bdc9e1",
"#74a9cf",
"#2b8cbe",
"#045a8d"
],
"blind": "ok",
"print": "maybe",
"screen": "maybe",
"copy": "bad"
},
{
"colors": [
"#f1eef6",
"#d0d1e6",
"#a6bddb",
"#74a9cf",
"#2b8cbe",
"#045a8d"
],
"blind": "ok",
"print": "bad",
"screen": "bad",
"copy": "bad"
},
{
"colors": [
"#f1eef6",
"#d0d1e6",
"#a6bddb",
"#74a9cf",
"#3690c0",
"#0570b0",
"#034e7b"
],
"blind": "ok",
"print": "bad",
"screen": "bad",
"copy": "bad"
},
{
"colors": [
"#fff7fb",
"#ece7f2",
"#d0d1e6",
"#a6bddb",
"#74a9cf",
"#3690c0",
"#0570b0",
"#034e7b"
],
"blind": "ok",
"print": "bad",
"screen": "bad",
"copy": "bad"
},
{
"colors": [
"#fff7fb",
"#ece7f2",
"#d0d1e6",
"#a6bddb",
"#74a9cf",
"#3690c0",
"#0570b0",
"#045a8d",
"#023858"
],
"blind": "ok",
"print": "bad",
"screen": "bad",
"copy": "bad"
}
],
"type": "seq"
},
{
"name": "BuPu",
"sets": [
{
"colors": [
"#e0ecf4",
"#9ebcda",
"#8856a7"
],
"blind": "ok",
"print": "ok",
"screen": "ok",
"copy": "ok"
},
{
"colors": [
"#edf8fb",
"#b3cde3",
"#8c96c6",
"#88419d"
],
"blind": "ok",
"print": "ok",
"screen": "ok",
"copy": "maybe"
},
{
"colors": [
"#edf8fb",
"#b3cde3",
"#8c96c6",
"#8856a7",
"#810f7c"
],
"blind": "ok",
"print": "maybe",
"screen": "ok",
"copy": "bad"
},
{
"colors": [
"#edf8fb",
"#bfd3e6",
"#9ebcda",
"#8c96c6",
"#8856a7",
"#810f7c"
],
"blind": "ok",
"print": "maybe",
"screen": "bad",
"copy": "bad"
},
{
"colors": [
"#edf8fb",
"#bfd3e6",
"#9ebcda",
"#8c96c6",
"#8c6bb1",
"#88419d",
"#6e016b"
],
"blind": "ok",
"print": "bad",
"screen": "bad",
"copy": "bad"
},
{
"colors": [
"#f7fcfd",
"#e0ecf4",
"#bfd3e6",
"#9ebcda",
"#8c96c6",
"#8c6bb1",
"#88419d",
"#6e016b"
],
"blind": "ok",
"print": "bad",
"screen": "bad",
"copy": "bad"
},
{
"colors": [
"#f7fcfd",
"#e0ecf4",
"#bfd3e6",
"#9ebcda",
"#8c96c6",
"#8c6bb1",
"#88419d",
"#810f7c",
"#4d004b"
],
"blind": "ok",
"print": "bad",
"screen": "bad",
"copy": "bad"
}
],
"type": "seq"
},
{
"name": "Oranges",
"sets": [
{
"colors": [
"#fee6ce",
"#fdae6b",
"#e6550d"
],
"blind": "ok",
"print": "ok",
"screen": "ok",
"copy": "ok"
},
{
"colors": [
"#feedde",
"#fdbe85",
"#fd8d3c",
"#d94701"
],
"blind": "ok",
"print": "maybe",
"screen": "ok",
"copy": "maybe"
},
{
"colors": [
"#feedde",
"#fdbe85",
"#fd8d3c",
"#e6550d",
"#a63603"
],
"blind": "ok",
"print": "bad",
"screen": "ok",
"copy": "maybe"
},
{
"colors": [
"#feedde",
"#fdd0a2",
"#fdae6b",
"#fd8d3c",
"#e6550d",
"#a63603"
],
"blind": "ok",
"print": "bad",
"screen": "bad",
"copy": "bad"
},
{
"colors": [
"#feedde",
"#fdd0a2",
"#fdae6b",
"#fd8d3c",
"#f16913",
"#d94801",
"#8c2d04"
],
"blind": "ok",
"print": "bad",
"screen": "bad",
"copy": "bad"
},
{
"colors": [
"#fff5eb",
"#fee6ce",
"#fdd0a2",
"#fdae6b",
"#fd8d3c",
"#f16913",
"#d94801",
"#8c2d04"
],
"blind": "ok",
"print": "bad",
"screen": "bad",
"copy": "bad"
},
{
"colors": [
"#fff5eb",
"#fee6ce",
"#fdd0a2",
"#fdae6b",
"#fd8d3c",
"#f16913",
"#d94801",
"#a63603",
"#7f2704"
],
"blind": "ok",
"print": "bad",
"screen": "bad",
"copy": "bad"
}
],
"type": "seq"
},
{
"name": "BuGn",
"sets": [
{
"colors": [
"#e5f5f9",
"#99d8c9",
"#2ca25f"
],
"blind": "ok",
"print": "ok",
"screen": "ok",
"copy": "ok"
},
{
"colors": [
"#edf8fb",
"#b2e2e2",
"#66c2a4",
"#238b45"
],
"blind": "ok",
"print": "ok",
"screen": "maybe",
"copy": "maybe"
},
{
"colors": [
"#edf8fb",
"#b2e2e2",
"#66c2a4",
"#2ca25f",
"#006d2c"
],
"blind": "ok",
"print": "maybe",
"screen": "bad",
"copy": "bad"
},
{
"colors": [
"#edf8fb",
"#ccece6",
"#99d8c9",
"#66c2a4",
"#2ca25f",
"#006d2c"
],
"blind": "ok",
"print": "bad",
"screen": "bad",
"copy": "bad"
},
{
"colors": [
"#edf8fb",
"#ccece6",
"#99d8c9",
"#66c2a4",
"#41ae76",
"#238b45",
"#005824"
],
"blind": "ok",
"print": "bad",
"screen": "bad",
"copy": "bad"
},
{
"colors": [
"#f7fcfd",
"#e5f5f9",
"#ccece6",
"#99d8c9",
"#66c2a4",
"#41ae76",
"#238b45",
"#005824"
],
"blind": "ok",
"print": "bad",
"screen": "bad",
"copy": "bad"
},
{
"colors": [
"#f7fcfd",
"#e5f5f9",
"#ccece6",
"#99d8c9",
"#66c2a4",
"#41ae76",
"#238b45",
"#006d2c",
"#00441b"
],
"blind": "ok",
"print": "bad",
"screen": "bad",
"copy": "bad"
}
],
"type": "seq"
},
{
"name": "YlOrBr",
"sets": [
{
"colors": [
"#fff7bc",
"#fec44f",
"#d95f0e"
],
"blind": "ok",
"print": "ok",
"screen": "ok",
"copy": "ok"
},
{
"colors": [
"#ffffd4",
"#fed98e",
"#fe9929",
"#cc4c02"
],
"blind": "ok",
"print": "ok",
"screen": "maybe",
"copy": "maybe"
},
{
"colors": [
"#ffffd4",
"#fed98e",
"#fe9929",
"#d95f0e",
"#993404"
],
"blind": "ok",
"print": "maybe",
"screen": "bad",
"copy": "maybe"
},
{
"colors": [
"#ffffd4",
"#fee391",
"#fec44f",
"#fe9929",
"#d95f0e",
"#993404"
],
"blind": "ok",
"print": "bad",
"screen": "bad",
"copy": "bad"
},
{
"colors": [
"#ffffd4",
"#fee391",
"#fec44f",
"#fe9929",
"#ec7014",
"#cc4c02",
"#8c2d04"
],
"blind": "ok",
"print": "bad",
"screen": "bad",
"copy": "bad"
},
{
"colors": [
"#ffffe5",
"#fff7bc",
"#fee391",
"#fec44f",
"#fe9929",
"#ec7014",
"#cc4c02",
"#8c2d04"
],
"blind": "ok",
"print": "bad",
"screen": "bad",
"copy": "bad"
},
{
"colors": [
"#ffffe5",
"#fff7bc",
"#fee391",
"#fec44f",
"#fe9929",
"#ec7014",
"#cc4c02",
"#993404",
"#662506"
],
"blind": "ok",
"print": "bad",
"screen": "bad",
"copy": "bad"
}
],
"type": "seq"
},
{
"name": "YlGn",
"sets": [
{
"colors": [
"#f7fcb9",
"#addd8e",
"#31a354"
],
"blind": "ok",
"print": "ok",
"screen": "ok",
"copy": "ok"
},
{
"colors": [
"#ffffcc",
"#c2e699",
"#78c679",
"#238443"
],
"blind": "ok",
"print": "ok",
"screen": "ok",
"copy": "maybe"
},
{
"colors": [
"#ffffcc",
"#c2e699",
"#78c679",
"#31a354",
"#006837"
],
"blind": "ok",
"print": "ok",
"screen": "ok",
"copy": "bad"
},
{
"colors": [
"#ffffcc",
"#d9f0a3",
"#addd8e",
"#78c679",
"#31a354",
"#006837"
],
"blind": "ok",
"print": "bad",
"screen": "bad",
"copy": "bad"
},
{
"colors": [
"#ffffcc",
"#d9f0a3",
"#addd8e",
"#78c679",
"#41ab5d",
"#238443",
"#005a32"
],
"blind": "ok",
"print": "bad",
"screen": "bad",
"copy": "bad"
},
{
"colors": [
"#ffffe5",
"#f7fcb9",
"#d9f0a3",
"#addd8e",
"#78c679",
"#41ab5d",
"#238443",
"#005a32"
],
"blind": "ok",
"print": "bad",
"screen": "bad",
"copy": "bad"
},
{
"colors": [
"#ffffe5",
"#f7fcb9",
"#d9f0a3",
"#addd8e",
"#78c679",
"#41ab5d",
"#238443",
"#006837",
"#004529"
],
"blind": "ok",
"print": "bad",
"screen": "bad",
"copy": "bad"
}
],
"type": "seq"
},
{
"name": "Reds",
"sets": [
{
"colors": [
"#fee0d2",
"#fc9272",
"#de2d26"
],
"blind": "ok",
"print": "ok",
"screen": "ok",
"copy": "ok"
},
{
"colors": [
"#fee5d9",
"#fcae91",
"#fb6a4a",
"#cb181d"
],
"blind": "ok",
"print": "maybe",
"screen": "maybe",
"copy": "maybe"
},
{
"colors": [
"#fee5d9",
"#fcae91",
"#fb6a4a",
"#de2d26",
"#a50f15"
],
"blind": "ok",
"print": "maybe",
"screen": "bad",
"copy": "bad"
},
{
"colors": [
"#fee5d9",
"#fcbba1",
"#fc9272",
"#fb6a4a",
"#de2d26",
"#a50f15"
],
"blind": "ok",
"print": "bad",
"screen": "bad",
"copy": "bad"
},
{
"colors": [
"#fee5d9",
"#fcbba1",
"#fc9272",
"#fb6a4a",
"#ef3b2c",
"#cb181d",
"#99000d"
],
"blind": "ok",
"print": "bad",
"screen": "bad",
"copy": "bad"
},
{
"colors": [
"#fff5f0",
"#fee0d2",
"#fcbba1",
"#fc9272",
"#fb6a4a",
"#ef3b2c",
"#cb181d",
"#99000d"
],
"blind": "ok",
"print": "bad",
"screen": "bad",
"copy": "bad"
},
{
"colors": [
"#fff5f0",
"#fee0d2",
"#fcbba1",
"#fc9272",
"#fb6a4a",
"#ef3b2c",
"#cb181d",
"#a50f15",
"#67000d"
],
"blind": "ok",
"print": "bad",
"screen": "bad",
"copy": "bad"
}
],
"type": "seq"
},
{
"name": "RdPu",
"sets": [
{
"colors": [
"#fde0dd",
"#fa9fb5",
"#c51b8a"
],
"blind": "ok",
"print": "ok",
"screen": "ok",
"copy": "ok"
},
{
"colors": [
"#feebe2",
"#fbb4b9",
"#f768a1",
"#ae017e"
],
"blind": "ok",
"print": "ok",
"screen": "ok",
"copy": "maybe"
},
{
"colors": [
"#feebe2",
"#fbb4b9",
"#f768a1",
"#c51b8a",
"#7a0177"
],
"blind": "ok",
"print": "ok",
"screen": "ok",
"copy": "bad"
},
{
"colors": [
"#feebe2",
"#fcc5c0",
"#fa9fb5",
"#f768a1",
"#c51b8a",
"#7a0177"
],
"blind": "ok",
"print": "maybe",
"screen": "bad",
"copy": "bad"
},
{
"colors": [
"#feebe2",
"#fcc5c0",
"#fa9fb5",
"#f768a1",
"#dd3497",
"#ae017e",
"#7a0177"
],
"blind": "ok",
"print": "bad",
"screen": "bad",
"copy": "bad"
},
{
"colors": [
"#fff7f3",
"#fde0dd",
"#fcc5c0",
"#fa9fb5",
"#f768a1",
"#dd3497",
"#ae017e",
"#7a0177"
],
"blind": "ok",
"print": "bad",
"screen": "bad",
"copy": "bad"
},
{
"colors": [
"#fff7f3",
"#fde0dd",
"#fcc5c0",
"#fa9fb5",
"#f768a1",
"#dd3497",
"#ae017e",
"#7a0177",
"#49006a"
],
"blind": "ok",
"print": "bad",
"screen": "bad",
"copy": "bad"
}
],
"type": "seq"
},
{
"name": "Greens",
"sets": [
{
"colors": [
"#e5f5e0",
"#a1d99b",
"#31a354"
],
"blind": "ok",
"print": "ok",
"screen": "ok",
"copy": "ok"
},
{
"colors": [
"#edf8e9",
"#bae4b3",
"#74c476",
"#238b45"
],
"blind": "ok",
"print": "bad",
"screen": "maybe",
"copy": "maybe"
},
{
"colors": [
"#edf8e9",
"#bae4b3",
"#74c476",
"#31a354",
"#006d2c"
],
"blind": "ok",
"print": "bad",
"screen": "bad",
"copy": "bad"
},
{
"colors": [
"#edf8e9",
"#c7e9c0",
"#a1d99b",
"#74c476",
"#31a354",
"#006d2c"
],
"blind": "ok",
"print": "bad",
"screen": "bad",
"copy": "bad"
},
{
"colors": [
"#edf8e9",
"#c7e9c0",
"#a1d99b",
"#74c476",
"#41ab5d",
"#238b45",
"#005a32"
],
"blind": "ok",
"print": "bad",
"screen": "bad",
"copy": "bad"
},
{
"colors": [
"#f7fcf5",
"#e5f5e0",
"#c7e9c0",
"#a1d99b",
"#74c476",
"#41ab5d",
"#238b45",
"#005a32"
],
"blind": "ok",
"print": "bad",
"screen": "bad",
"copy": "bad"
},
{
"colors": [
"#f7fcf5",
"#e5f5e0",
"#c7e9c0",
"#a1d99b",
"#74c476",
"#41ab5d",
"#238b45",
"#006d2c",
"#00441b"
],
"blind": "ok",
"print": "bad",
"screen": "bad",
"copy": "bad"
}
],
"type": "seq"
},
{
"name": "YlGnBu",
"sets": [
{
"colors": [
"#edf8b1",
"#7fcdbb",
"#2c7fb8"
],
"blind": "ok",
"print": "ok",
"screen": "ok",
"copy": "ok"
},
{
"colors": [
"#ffffcc",
"#a1dab4",
"#41b6c4",
"#225ea8"
],
"blind": "ok",
"print": "ok",
"screen": "ok",
"copy": "maybe"
},
{
"colors": [
"#ffffcc",
"#a1dab4",
"#41b6c4",
"#2c7fb8",
"#253494"
],
"blind": "ok",
"print": "ok",
"screen": "maybe",
"copy": "bad"
},
{
"colors": [
"#ffffcc",
"#c7e9b4",
"#7fcdbb",
"#41b6c4",
"#2c7fb8",
"#253494"
],
"blind": "ok",
"print": "maybe",
"screen": "bad",
"copy": "bad"
},
{
"colors": [
"#ffffcc",
"#c7e9b4",
"#7fcdbb",
"#41b6c4",
"#1d91c0",
"#225ea8",
"#0c2c84"
],
"blind": "ok",
"print": "maybe",
"screen": "bad",
"copy": "bad"
},
{
"colors": [
"#ffffd9",
"#edf8b1",
"#c7e9b4",
"#7fcdbb",
"#41b6c4",
"#1d91c0",
"#225ea8",
"#0c2c84"
],
"blind": "ok",
"print": "maybe",
"screen": "bad",
"copy": "bad"
},
{
"colors": [
"#ffffd9",
"#edf8b1",
"#c7e9b4",
"#7fcdbb",
"#41b6c4",
"#1d91c0",
"#225ea8",
"#253494",
"#081d58"
],
"blind": "ok",
"print": "bad",
"screen": "bad",
"copy": "bad"
}
],
"type": "seq"
},
{
"name": "Purples",
"sets": [
{
"colors": [
"#efedf5",
"#bcbddc",
"#756bb1"
],
"blind": "ok",
"print": "ok",
"screen": "ok",
"copy": "ok"
},
{
"colors": [
"#f2f0f7",
"#cbc9e2",
"#9e9ac8",
"#6a51a3"
],
"blind": "ok",
"print": "bad",
"screen": "bad",
"copy": "maybe"
},
{
"colors": [
"#f2f0f7",
"#cbc9e2",
"#9e9ac8",
"#756bb1",
"#54278f"
],
"blind": "ok",
"print": "bad",
"screen": "bad",
"copy": "bad"
},
{
"colors": [
"#f2f0f7",
"#dadaeb",
"#bcbddc",
"#9e9ac8",
"#756bb1",
"#54278f"
],
"blind": "ok",
"print": "bad",
"screen": "bad",
"copy": "bad"
},
{
"colors": [
"#f2f0f7",
"#dadaeb",
"#bcbddc",
"#9e9ac8",
"#807dba",
"#6a51a3",
"#4a1486"
],
"blind": "ok",
"print": "bad",
"screen": "bad",
"copy": "bad"
},
{
"colors": [
"#fcfbfd",
"#efedf5",
"#dadaeb",
"#bcbddc",
"#9e9ac8",
"#807dba",
"#6a51a3",
"#4a1486"
],
"blind": "ok",
"print": "bad",
"screen": "bad",
"copy": "bad"
},
{
"colors": [
"#fcfbfd",
"#efedf5",
"#dadaeb",
"#bcbddc",
"#9e9ac8",
"#807dba",
"#6a51a3",
"#54278f",
"#3f007d"
],
"blind": "ok",
"print": "bad",
"screen": "bad",
"copy": "bad"
}
],
"type": "seq"
},
{
"name": "GnBu",
"sets": [
{
"colors": [
"#e0f3db",
"#a8ddb5",
"#43a2ca"
],
"blind": "ok",
"print": "ok",
"screen": "ok",
"copy": "ok"
},
{
"colors": [
"#f0f9e8",
"#bae4bc",
"#7bccc4",
"#2b8cbe"
],
"blind": "ok",
"print": "ok",
"screen": "ok",
"copy": "maybe"
},
{
"colors": [
"#f0f9e8",
"#bae4bc",
"#7bccc4",
"#43a2ca",
"#0868ac"
],
"blind": "ok",
"print": "ok",
"screen": "maybe",
"copy": "bad"
},
{
"colors": [
"#f0f9e8",
"#ccebc5",
"#a8ddb5",
"#7bccc4",
"#43a2ca",
"#0868ac"
],
"blind": "ok",
"print": "maybe",
"screen": "bad",
"copy": "bad"
},
{
"colors": [
"#f0f9e8",
"#ccebc5",
"#a8ddb5",
"#7bccc4",
"#4eb3d3",
"#2b8cbe",
"#08589e"
],
"blind": "ok",
"print": "maybe",
"screen": "bad",
"copy": "bad"
},
{
"colors": [
"#f7fcf0",
"#e0f3db",
"#ccebc5",
"#a8ddb5",
"#7bccc4",
"#4eb3d3",
"#2b8cbe",
"#08589e"
],
"blind": "ok",
"print": "maybe",
"screen": "bad",
"copy": "bad"
},
{
"colors": [
"#f7fcf0",
"#e0f3db",
"#ccebc5",
"#a8ddb5",
"#7bccc4",
"#4eb3d3",
"#2b8cbe",
"#0868ac",
"#084081"
],
"blind": "ok",
"print": "bad",
"screen": "bad",
"copy": "bad"
}
],
"type": "seq"
},
{
"name": "Greys",
"sets": [
{
"colors": [
"#f0f0f0",
"#bdbdbd",
"#636363"
],
"blind": "ok",
"print": "ok",
"screen": "ok",
"copy": "ok"
},
{
"colors": [
"#f7f7f7",
"#cccccc",
"#969696",
"#525252"
],
"blind": "ok",
"print": "ok",
"screen": "maybe",
"copy": "bad"
},
{
"colors": [
"#f7f7f7",
"#cccccc",
"#969696",
"#636363",
"#252525"
],
"blind": "ok",
"print": "maybe",
"screen": "bad",
"copy": "bad"
},
{
"colors": [
"#f7f7f7",
"#d9d9d9",
"#bdbdbd",
"#969696",
"#636363",
"#252525"
],
"blind": "ok",
"print": "bad",
"screen": "bad",
"copy": "bad"
},
{
"colors": [
"#f7f7f7",
"#d9d9d9",
"#bdbdbd",
"#969696",
"#737373",
"#525252",
"#252525"
],
"blind": "ok",
"print": "bad",
"screen": "bad",
"copy": "bad"
},
{
"colors": [
"#ffffff",
"#f0f0f0",
"#d9d9d9",
"#bdbdbd",
"#969696",
"#737373",
"#525252",
"#252525"
],
"blind": "ok",
"print": "bad",
"screen": "bad",
"copy": "bad"
},
{
"colors": [
"#ffffff",
"#f0f0f0",
"#d9d9d9",
"#bdbdbd",
"#969696",
"#737373",
"#525252",
"#252525",
"#000000"
],
"blind": "ok",
"print": "bad",
"screen": "bad",
"copy": "bad"
}
],
"type": "seq"
},
{
"name": "YlOrRd",
"sets": [
{
"colors": [
"#ffeda0",
"#feb24c",
"#f03b20"
],
"blind": "ok",
"print": "ok",
"screen": "ok",
"copy": "ok"
},
{
"colors": [
"#ffffb2",
"#fecc5c",
"#fd8d3c",
"#e31a1c"
],
"blind": "ok",
"print": "ok",
"screen": "maybe",
"copy": "maybe"
},
{
"colors": [
"#ffffb2",
"#fecc5c",
"#fd8d3c",
"#f03b20",
"#bd0026"
],
"blind": "ok",
"print": "maybe",
"screen": "maybe",
"copy": "maybe"
},
{
"colors": [
"#ffffb2",
"#fed976",
"#feb24c",
"#fd8d3c",
"#f03b20",
"#bd0026"
],
"blind": "ok",
"print": "maybe",
"screen": "bad",
"copy": "bad"
},
{
"colors": [
"#ffffb2",
"#fed976",
"#feb24c",
"#fd8d3c",
"#fc4e2a",
"#e31a1c",
"#b10026"
],
"blind": "ok",
"print": "bad",
"screen": "bad",
"copy": "bad"
},
{
"colors": [
"#ffffcc",
"#ffeda0",
"#fed976",
"#feb24c",
"#fd8d3c",
"#fc4e2a",
"#e31a1c",
"#b10026"
],
"blind": "ok",
"print": "bad",
"screen": "bad",
"copy": "bad"
},
{
"colors": [
"#ffffcc",
"#ffeda0",
"#fed976",
"#feb24c",
"#fd8d3c",
"#fc4e2a",
"#e31a1c",
"#bd0026",
"#800026"
],
"blind": "ok",
"print": "bad",
"screen": "bad",
"copy": "bad"
}
],
"type": "seq"
},
{
"name": "PuRd",
"sets": [
{
"colors": [
"#e7e1ef",
"#c994c7",
"#dd1c77"
],
"blind": "ok",
"print": "ok",
"screen": "ok",
"copy": "ok"
},
{
"colors": [
"#f1eef6",
"#d7b5d8",
"#df65b0",
"#ce1256"
],
"blind": "ok",
"print": "ok",
"screen": "ok",
"copy": "maybe"
},
{
"colors": [
"#f1eef6",
"#d7b5d8",
"#df65b0",
"#dd1c77",
"#980043"
],
"blind": "ok",
"print": "ok",
"screen": "ok",
"copy": "bad"
},
{
"colors": [
"#f1eef6",
"#d4b9da",
"#c994c7",
"#df65b0",
"#dd1c77",
"#980043"
],
"blind": "ok",
"print": "bad",
"screen": "bad",
"copy": "bad"
},
{
"colors": [
"#f1eef6",
"#d4b9da",
"#c994c7",
"#df65b0",
"#e7298a",
"#ce1256",
"#91003f"
],
"blind": "ok",
"print": "bad",
"screen": "bad",
"copy": "bad"
},
{
"colors": [
"#f7f4f9",
"#e7e1ef",
"#d4b9da",
"#c994c7",
"#df65b0",
"#e7298a",
"#ce1256",
"#91003f"
],
"blind": "ok",
"print": "bad",
"screen": "bad",
"copy": "bad"
},
{
"colors": [
"#f7f4f9",
"#e7e1ef",
"#d4b9da",
"#c994c7",
"#df65b0",
"#e7298a",
"#ce1256",
"#980043",
"#67001f"
],
"blind": "ok",
"print": "bad",
"screen": "bad",
"copy": "bad"
}
],
"type": "seq"
},
{
"name": "Blues",
"sets": [
{
"colors": [
"#deebf7",
"#9ecae1",
"#3182bd"
],
"blind": "ok",
"print": "ok",
"screen": "ok",
"copy": "ok"
},
{
"colors": [
"#eff3ff",
"#bdd7e7",
"#6baed6",
"#2171b5"
],
"blind": "ok",
"print": "maybe",
"screen": "maybe",
"copy": "bad"
},
{
"colors": [
"#eff3ff",
"#bdd7e7",
"#6baed6",
"#3182bd",
"#08519c"
],
"blind": "ok",
"print": "bad",
"screen": "bad",
"copy": "bad"
},
{
"colors": [
"#eff3ff",
"#c6dbef",
"#9ecae1",
"#6baed6",
"#3182bd",
"#08519c"
],
"blind": "ok",
"print": "bad",
"screen": "bad",
"copy": "bad"
},
{
"colors": [
"#eff3ff",
"#c6dbef",
"#9ecae1",
"#6baed6",
"#4292c6",
"#2171b5",
"#084594"
],
"blind": "ok",
"print": "bad",
"screen": "bad",
"copy": "bad"
},
{
"colors": [
"#f7fbff",
"#deebf7",
"#c6dbef",
"#9ecae1",
"#6baed6",
"#4292c6",
"#2171b5",
"#084594"
],
"blind": "ok",
"print": "bad",
"screen": "bad",
"copy": "bad"
},
{
"colors": [
"#f7fbff",
"#deebf7",
"#c6dbef",
"#9ecae1",
"#6baed6",
"#4292c6",
"#2171b5",
"#08519c",
"#08306b"
],
"blind": "ok",
"print": "bad",
"screen": "bad",
"copy": "bad"
}
],
"type": "seq"
},
{
"name": "PuBuGn",
"sets": [
{
"colors": [
"#ece2f0",
"#a6bddb",
"#1c9099"
],
"blind": "ok",
"print": "ok",
"screen": "ok",
"copy": "ok"
},
{
"colors": [
"#f6eff7",
"#bdc9e1",
"#67a9cf",
"#02818a"
],
"blind": "ok",
"print": "maybe",
"screen": "ok",
"copy": "maybe"
},
{
"colors": [
"#f6eff7",
"#bdc9e1",
"#67a9cf",
"#1c9099",
"#016c59"
],
"blind": "ok",
"print": "maybe",
"screen": "maybe",
"copy": "bad"
},
{
"colors": [
"#f6eff7",
"#d0d1e6",
"#a6bddb",
"#67a9cf",
"#1c9099",
"#016c59"
],
"blind": "ok",
"print": "bad",
"screen": "bad",
"copy": "bad"
},
{
"colors": [
"#f6eff7",
"#d0d1e6",
"#a6bddb",
"#67a9cf",
"#3690c0",
"#02818a",
"#016450"
],
"blind": "ok",
"print": "bad",
"screen": "bad",
"copy": "bad"
},
{
"colors": [
"#fff7fb",
"#ece2f0",
"#d0d1e6",
"#a6bddb",
"#67a9cf",
"#3690c0",
"#02818a",
"#016450"
],
"blind": "ok",
"print": "bad",
"screen": "bad",
"copy": "bad"
},
{
"colors": [
"#fff7fb",
"#ece2f0",
"#d0d1e6",
"#a6bddb",
"#67a9cf",
"#3690c0",
"#02818a",
"#016c59",
"#014636"
],
"blind": "ok",
"print": "bad",
"screen": "bad",
"copy": "bad"
}
],
"type": "seq"
}
];
//export default colorbrewer;
/* generated with Nodejs
// get colorbrewer definition from http://colorbrewer2.org/colorbrewer_schemes.js,
// append code below,
// run code with Node
let stdout = process.stdout;
const result = [];
for (let key in colorbrewer) {
const color = {name: key, sets: []}
color.type = colorbrewer[key].properties.type;
for (property in colorbrewer[key]) {
if (property != 'properties') {
color.sets.push({colors:colorbrewer[key][property].map(color=>tinycolor(color).toHexString())});
}
}
if (colorbrewer[key].properties.blind.length > 1) {
colorbrewer[key].properties.blind.forEach((prop, index)=>{if (index < color.sets.length) {color.sets[index].blind=prop===0?"bad":prop===1?"ok":"maybe"}});
} else {
const value = colorbrewer[key].properties.blind[0];
color.sets.forEach(set=>set.blind=value===0?"bad":value===1?"ok":"maybe");
}
if (colorbrewer[key].properties.print.length > 1) {
colorbrewer[key].properties.print.forEach((prop, index)=>{if (index < color.sets.length) {color.sets[index].print=prop===0?"bad":prop===1?"ok":"maybe"}});
} else {
const value = colorbrewer[key].properties.print[0];
color.sets.forEach(set=>set.print=value===0?"bad":value===1?"ok":"maybe");
}
if (colorbrewer[key].properties.screen.length > 1) {
colorbrewer[key].properties.screen.forEach((prop, index)=>{if (index < color.sets.length) {color.sets[index].screen=prop===0?"bad":prop===1?"ok":"maybe"}});
} else {
const value = colorbrewer[key].properties.screen[0];
color.sets.forEach(set=>set.screen=value===0?"bad":value===1?"ok":"maybe");
}
if (colorbrewer[key].properties.copy.length > 1) {
colorbrewer[key].properties.copy.forEach((prop, index)=>{if (index < color.sets.length) {color.sets[index].copy=prop===0?"bad":prop===1?"ok":"maybe"}});
} else {
const value = colorbrewer[key].properties.copy[0];
color.sets.forEach(set=>set.copy=value===0?"bad":value===1?"ok":"maybe");
}
result.push(color);
}
stdout.write(JSON.stringify(result, null, 2));
*/
\ No newline at end of file
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Info</title>
<script>
let layerInfo = allLayerInfo = null;
let sortFieldName = '';
let sortAscending = true;
function sortBy(name) {
let fieldname;
switch(name) {
case 'schema':
fieldname = 'f_table_schema';
break;
case 'name':
fieldname = 'f_table_name';
break;
case 'geom_column':
fieldname = 'f_geometry_column';
break;
case 'geom_type':
fieldname = 'type';
break;
case 'dim':
fieldname = 'coord_dimension';
break;
case 'type':
fieldname = 'table_type';
break;
case 'count':
fieldname = 'estimated_rows'
break;
default:
fieldname = name;
}
if (sortFieldName != fieldname) {
sortFieldName = fieldname;
sortAscending = true;
} else {
sortAscending = !sortAscending;
}
updateLayerInfo(fieldname, sortAscending);
}
function updateLayerInfo(field, ascending) {
const table = document.querySelector('#layerinfo');
switch (field) {
case 'f_table_schema':
layerInfo.sort((item1, item2) => ('' + item1.f_table_schema + item1.f_table_name).localeCompare(item2.f_table_schema + item2.f_table_name))
break;
case 'f_table_name':
layerInfo.sort((item1, item2) => ('' + item1[field]).localeCompare(item2[field]));
break;
case 'f_geometry_column':
case 'type':
case 'table_type':
case 'coord_dimension':
layerInfo.sort((item1, item2) => ('' + item1[field] + '.' + item1.f_table_schema + '.' + item1.f_table_name).localeCompare(item2[field] + '.' + item2.f_table_schema + '.' + item2.f_table_name));
break;
case 'srid':
layerInfo.sort((item1, item2)=>item1.srid-item2.srid?item1.srid-item2.srid:('' + item1.f_table_schema + item1.f_table_name).localeCompare(item2.f_table_schema + item2.f_table_name));
break;
case 'estimated_rows':
layerInfo.sort((item1, item2)=>item1.estimated_rows-item2.estimated_rows);
}
if (!ascending) {
layerInfo.reverse();
}
table.innerHTML = '<tr><th>schema</th><th>name</th><th>geom_column</th><th>srid</th><th>geom_type</th><th>dim</th><th>type</th><th>count</th></tr>' +
layerInfo.map(item=>`<tr>
<td>${item.f_table_schema}</td>
<td><a href="tableinfo.html?table=${item.f_table_schema}.${item.f_table_name}&geom_column=${item.f_geometry_column}&srid=${item.srid}&geomtype=${item.type}&dimensions=${item.coord_dimension}&estimated_rows=${item.estimated_rows}">${item.f_table_name}</a></td>
<td>${item.f_geometry_column}</td>
<td>${item.srid}</td>
<td>${item.type}</td>
<td>${item.coord_dimension}D</td>
<td>${item.table_type}</td>
<td>${item.estimated_rows}</td></tr>`).join('\n');
const tableHeaders = document.querySelectorAll('tr > th');
for (let i = 0; i < tableHeaders.length; i++) {
tableHeaders[i].innerHTML = `<a href="#" onclick="sortBy('${tableHeaders[i].textContent}')">${tableHeaders[i].textContent}</a>`
}
}
function filterLayerInfo() {
const filterWords = document.querySelector('#filter')
.value.split(' ')
.filter(word=>word !== '')
.map(word=>word.toLocaleLowerCase());
if (filterWords.length) {
layerInfo = allLayerInfo.filter(layer=>{
const layerText = Object.values(layer).join(' ').toLocaleLowerCase();
return filterWords.filter(word=>layerText.indexOf(word)>-1).length == filterWords.length;
})
} else {
layerInfo = allLayerInfo;
}
updateLayerInfo(sortFieldName, sortAscending);
}
function init() {
fetch('data/list_layers').then(response=>{
if (response.ok) {
response.json().then(json=> {
layerInfo = allLayerInfo = json;
filterLayerInfo();
sortBy('schema');
})
} else {
layerInfo = allLayerInfo = [];
response.json().then(json =>{
document.querySelector('#layerinfo').innerHTML = `<tr><td>Error fetching layers: ${json.error}</td></tr>`
}).catch(err => {
document.querySelector('#layerinfo').innerHTML = `<tr><td>Error fetching layers: ${err}</td></tr>`
});
}
})
}
</script>
</head>
<body onload="init()">
<h1>Layers in database</h1>
<input id="filter" type="text" placeholder="Search..." size="20" oninput="filterLayerInfo()"><br>
<table id="layerinfo"></table>
</body>
</html>
\ No newline at end of file
/* source https://codepen.io/TheDutchCoder/pen/gholk */
/* The loader container */
.loader {
position: absolute;
top: 50%;
left: 50%;
width: 200px;
height: 200px;
margin-top: -100px;
margin-left: -100px;
perspective: 400px;
/* transform-type: preserve-3d; */
}
/* The dot */
.dot {
position: absolute;
top: 50%;
left: 50%;
z-index: 10;
width: 40px;
height: 40px;
margin-top: -20px;
margin-left: -80px;
/* transform-type: preserve-3d;*/
transform-origin: 80px 50%;
transform: rotateY(0);
background-color: #1e3f57;
animation: dot1 2000ms cubic-bezier(.56,.09,.89,.69) infinite;
}
.dot:nth-child(2) {
z-index: 9;
animation-delay: 150ms;
}
.dot:nth-child(3) {
z-index: 8;
animation-delay: 300ms;
}
.dot:nth-child(4) {
z-index: 7;
animation-delay: 450ms;
}
.dot:nth-child(5) {
z-index: 6;
animation-delay: 600ms;
}
.dot:nth-child(6) {
z-index: 5;
animation-delay: 750ms;
}
.dot:nth-child(7) {
z-index: 4;
animation-delay: 900ms;
}
.dot:nth-child(8) {
z-index: 3;
animation-delay: 1050ms;
}
@keyframes dot1 {
0% {
transform: rotateY(0) rotateZ(0) rotateX(0);
background-color: #1e3f57;
}
45% {
transform: rotateZ(180deg) rotateY(360deg) rotateX(90deg);
background-color: #6bb2cd;
animation-timing-function: cubic-bezier(.15,.62,.72,.98);
}
90%, 100% {
transform: rotateY(0) rotateZ(360deg) rotateX(180deg);
background-color: #1e3f57;
}
}
\ No newline at end of file
body,html {
font-family: Verdana, Geneva, Tahoma, sans-serif
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Info</title>
<script>
function replaceGeomType(url, geomtype) {
if (geomtype) {
const parts = url.split('&')
.map(part=>{
if (part.startsWith('geomtype=')) {
part = `geomtype=${geomtype}`
}
return part;
})
url = parts.join('&');
}
return url;
}
function addBboxllToLinks(bboxll,geomtype)
{
if (bboxll) {
document.querySelectorAll('#columns > li > a')
.forEach(a=>(a.setAttribute('href', replaceGeomType(a.getAttribute('href'),geomtype) + '&bboxll='+JSON.stringify(bboxll))))
}
}
function init() {
const urlParams = new URLSearchParams(window.location.search);
const fullTableName = urlParams.get('table');
const geomType = urlParams.get('geomtype');
const geomcolumn = urlParams.get('geom_column');
let bboxll = null;
document.querySelector('#tablename').innerHTML = fullTableName;
const parts = fullTableName.split('.');
const tableName = (parts.length > 1) ? parts[1] : parts[0];
fetch(`data/layer_columns/${fullTableName}`).then(response=>{
const list = document.querySelector('#columns');
if (response.ok) {
response.json().then(json=> {
for (item of json) {
const li = document.createElement('li');
li.innerHTML = `<a href="./attrinfo.html?table=${fullTableName}&geom_column=${geomcolumn?geomcolumn:''}&column=${item.field_name}&columntype=${item.field_type}&geomtype=${geomType}"><b>${item.field_name}</b></a> (${item.field_type})`
list.appendChild(li);
}
fetch(`api/bbox/${fullTableName}${geomcolumn?`?geom_column=${geomcolumn}`:''}`).then(response=>{
const bbox = document.querySelector('#bbox');
if (response.ok) {
response.json().then(json=> {
addBboxllToLinks(json.bboxll,json.geomtype);
bbox.innerHTML = `number of rows: ${json.allrows}<br>
number of geometries: ${json.geomrows}<br>
srid: EPSG:${json.srid}<br>
bbox lon/lat: ${json.bboxll?`sw: ${json.bboxll[0][0]},${json.bboxll[0][1]}, ne: ${json.bboxll[1][0]},${json.bboxll[1][1]}`: 'not defined'}<br>
bbox (EPSG:${json.srid}): ${json.srid?`ll: ${json.bboxsrid[0][0]},${json.bboxsrid[0][1]}, tr: ${json.bboxsrid[1][0]},${json.bboxsrid[1][1]}`: 'not defined'}<br>
`
})
} else {
response.json().then(json=>{
bbox.innerHTML = `Error getting bbox: ${json.error}`;
}).catch(err => {
bbox.innerHTML = `Error parsing bbox: ${err}`;
})
}
})
})
} else {
const li = document.createElement('li');
response.json().then(json=>{
li.innerHTML = `Error getting column info: ${json.error}`;
}).catch(err => {
li.innerHTML = `Error parsing column info: ${err}`;
})
list.appendChild(li);
}
})
}
</script>
</head>
<body onload="init()">
<h1>Layer info</h1>
<h2 id="tablename"></h2>
<div id="bbox">waiting for table stats...</div>
<ul id="columns"></ul>
<a href="info.html">Back to layer overview</a>
</body>
</html>
\ No newline at end of file
function roundToPrecision(number, precision, direction) {
let negative = (number < 0);
if (negative) {
number = -number;
direction = -direction;
}
let roundFunc = (direction < 0 ? Math.floor : direction === 0 ? Math.round : Math.ceil);
let exponent = (number === 0)?1:Math.floor(Math.log10(number));
let decimals = (exponent < precision)? precision - exponent : 0;
let fraction = number / Math.pow(10,exponent);
return Number((Math.pow(10, exponent) * roundFunc(fraction * Math.pow(10, precision)) / Math.pow(10, precision) * (negative ? -1 : 1)).toFixed(decimals));
}
function getIntervalClassTicks (min, max, classCount) {
let niceMin = roundToPrecision(min, 2, -1);
let niceMax = roundToPrecision(max, 2, 1);
let interval = (niceMax - niceMin) / classCount;
let result = [];
for (let i = 0; i < classCount; i++) {
result.push(roundToPrecision(niceMin + i * interval, 2, -1))
}
return {
min: niceMin,
max: niceMax,
classes: result
};
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Upload</title>
<!-- Filepond stylesheet -->
<link href="pgserver.css" rel="stylesheet">
</head>
<body>
<form action="upload" method="post" encType="multipart/form-data">
<input name="uploadfile" type="file"><br>
<input type="submit" value="upload">
</form>
</body>
</html>
\ No newline at end of file
const swaggerUi = require('swagger-ui-express');
const swaggerJSDoc = require('swagger-jsdoc');
const swaggerDefinition = {
info: {
// API informations (required)
title: 'PGServer', // Title (required)
version: '0.0.1', // Version (required)
description: 'PostGIS http api', // Description (optional)
},
tags: [
{
name: 'meta',
description: 'meta information for tables and views'
},
{
name: 'geodata',
description: 'features in common formats for direct mapping'
}
],
basePath: '/', // Base path (optional)
}
const swaggerJSDocOptions = {
swaggerDefinition,
apis: [
'./login.js',
'./mvt.js',
'./list_layers.js',
'./layer_columns.js',
'./bbox.js',
'./geojson.js',
'./geobuf.js',
'./query.js',
'./column_stats.js'
]
}
const swaggerSpec = swaggerJSDoc(swaggerJSDocOptions);
module.exports = function(app) {
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerSpec));
}
const express = require('express');
const fs = require('fs');
const fileUpload = require('express-fileupload');
module.exports = function(app) {
app.use(fileUpload({
useTempFiles: true,
tempFileDir : `${__dirname}/public/files/`
}));
app.post('/upload', (req, res) => {
let uploadFile = req.files.uploadfile;
const fileName = uploadFile.name;
res.json({
file: `${fileName}`
})
uploadFile.mv(
`${__dirname}/public/files/${fileName}`,
function (err) {
if (err) {
return res.status(500).send(err);
}
}
)
});
app.get('/upload', (req, res) =>{
url = req.query.fetch;
console.log(url);
res.json({
file: 'index.html'
});
})
app.delete('/upload', express.json({type: '*/*'}), (req, res) => {
fs.unlinkSync(`${__dirname}/public/files/${req.body.file}`);
res.json({
file: 'done'
});
});
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment