Skip to content

Commit

Permalink
Merge branch 'develop' into feature-#1720-cesium-data-catalog
Browse files Browse the repository at this point in the history
  • Loading branch information
robyngit committed Jan 12, 2022
2 parents 86fd21c + a8f99db commit 9da7824
Show file tree
Hide file tree
Showing 4 changed files with 177 additions and 43 deletions.
71 changes: 46 additions & 25 deletions src/js/models/maps/AssetColorPalette.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ define(
* @type {Object}
* @property {('categorical'|'continuous'|'classified')}
* [paletteType='categorical'] Set to 'categorical', 'continuous', or
* 'classified'. NOTE: Currently only categorical palettes are supported.
* 'classified'. NOTE: Currently only categorical and continuous palettes are
* supported.
* - Categorical: the color conditions will be interpreted such that one color
* represents a single value (e.g. a discrete palette).
* - Continuous: each color in the colors attribute will represent a point in a
Expand Down Expand Up @@ -79,8 +80,8 @@ define(
* @typedef {Object} ColorPaletteConfig
* @name MapConfig#ColorPaletteConfig
* @property {('categorical'|'continuous'|'classified')}
* [paletteType='categorical'] NOTE: Currently only categorical palettes are
* supported.
* [paletteType='categorical'] NOTE: Currently only categorical and continuous
* palettes are supported.
* - Categorical: the color conditions will be interpreted such that one color
* represents a single value (e.g. a discrete palette).
* - Continuous: each color in the colors attribute will represent a point in a
Expand Down Expand Up @@ -123,6 +124,10 @@ define(
if (paletteConfig && paletteConfig.colors) {
this.set('colors', new AssetColors(paletteConfig.colors))
}
// If a continuous palette has only 1 colour, then treat it as categorical
if (this.get('paletteType') === 'continuous' && this.get('colors').length === 1) {
this.set('paletteType', 'categorical')
}
}
catch (error) {
console.log(
Expand Down Expand Up @@ -185,34 +190,50 @@ define(
// }
// color = sortedColors[i].get('color');
} else if (type === 'continuous') {
// TODO: test

// For a continuous color palette, the value of the feature property must
// either match one of the values in the color palette, or be interpolated
// between the values in the palette.

// const sortedColors = colors.toArray().sort(function (a, b) {
// return a.get('value') - b.get('value')
// })
// let i = 0;
// while (i < sortedColors.length && propValue >= sortedColors[i].get('value')) {
// i++;
// }
// if (i === 0) {
// color = sortedColors[0].get('color');
// }
// else if (i === sortedColors.length) {
// color = sortedColors[i - 1].get('color');
// }
// else {
// const percent = (propValue - sortedColors[i - 1].get('value')) /
// (sortedColors[i].get('value') - sortedColors[i - 1].get('value'));
// color = sortedColors[i - 1].get('color').interpolate(sortedColors[i].get('color'), percent);
// }
// between the values in the palette.
const sortedColors = colors.toArray().sort(function (a, b) {
return a.get('value') - b.get('value')
})
let i = 0;
while (i < sortedColors.length && propValue >= sortedColors[i].get('value')) {
i++;
}
if (i === 0) {
color = sortedColors[0].get('color');
}
else if (i === sortedColors.length) {
color = sortedColors[i - 1].get('color');
}
else {
const percent = (propValue - sortedColors[i - 1].get('value')) /
(sortedColors[i].get('value') - sortedColors[i - 1].get('value'));
color = colorPalette.interpolate(sortedColors[i - 1].get('color'), sortedColors[i].get('color'), percent)
}
}
return color
},

/**
* Given two colors, returns a color that is a linear interpolation between the
* two colors.
* @param {AssetColor#Color} color1 The first color.
* @param {AssetColor#Color} color2 The second color.
* @param {number} fraction The percentage of the way between the two colors, 0-1.
* @returns {AssetColor#Color} The interpolated color.
*/
interpolate: function (color1, color2, fraction) {
const red = color1.red + fraction * (color2.red - color1.red)
const green = color1.green + fraction * (color2.green - color1.green)
const blue = color1.blue + fraction * (color2.blue - color1.blue)
return {
red: red,
green: green,
blue: blue
}
},

/**
* Gets the default color for the color palette, returns it as an object of RGB
* intestines between 0 and 1.
Expand Down
4 changes: 2 additions & 2 deletions src/js/templates/draftsTemplate.html
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@ <h2>Your Draft Submissions</h2>
<td><code><%= draft.value.id || "No ID" %></code></td>
<td><%= draft.friendlyTimeDiff || "No datetime" %></td>
<td class="draft-download">
<a class="btn btn-primary download" data-filename="<%= draft.value.title.substr(0, 50).replace(/[^a-zA-Z0-9-]/, '_') %>.xml">Download</a>
<a class="btn btn-primary download" data-filename="<%= draft.value.title? draft.value.title.substr(0, 50).replace(/[^a-zA-Z0-9-]/, '_') : 'metadata'%>.xml">Download</a>
<a class="btn copy-to-clipboard"><i class="icon icon-copy"></i> Copy to Clipboard</a>
<textarea id="draft-<%= i %>" style="display: none;" rows="6" cols="80"><%= draft.value.draft %></textarea>
<textarea id="draft-<%= i %>" style="display: none;" rows="6" cols="80"><%- draft.value.draft %></textarea>
</td>
</tr>
<% })%>
Expand Down
2 changes: 1 addition & 1 deletion src/js/themes/arctic/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ MetacatUI.AppConfig = Object.assign({
geoCoverage: true,
intellectualRights: true,
keywordSets: false,
methods: false,
methods: true,
samplingDescription: false,
studyExtentDescription: false,
taxonCoverage: false,
Expand Down
143 changes: 128 additions & 15 deletions src/js/views/maps/LegendView.js
Original file line number Diff line number Diff line change
Expand Up @@ -169,23 +169,25 @@ define(
}
}

// For categorical vector color palettes, in preview mode
if (colorPalette && mode === 'preview' && paletteType === 'categorical') {
this.renderCategoricalPreviewLegend(colorPalette)
}
// For imagery layers that do not have a color palette, in preview mode
else if (mode === 'preview' && typeof this.model.getThumbnail === 'function') {
if (!this.model.get('thumbnail')) {
this.listenToOnce(this.model, 'change:thumbnail', function () {
if (mode === 'preview') {
// For categorical vector color palettes, in preview mode
if (colorPalette && paletteType === 'categorical') {
this.renderCategoricalPreviewLegend(colorPalette)
} else if (colorPalette && paletteType === 'continuous') {
this.renderContinuousPreviewLegend(colorPalette)
}
// For imagery layers that do not have a color palette, in preview mode
else if (typeof this.model.getThumbnail === 'function') {
if (!this.model.get('thumbnail')) {
this.listenToOnce(this.model, 'change:thumbnail', function () {
this.renderImagePreviewLegend(this.model.get('thumbnail'))
})
} else {
this.renderImagePreviewLegend(this.model.get('thumbnail'))
})
} else {
this.renderImagePreviewLegend(this.model.get('thumbnail'))
}
}
}

// TODO:
// - preview continuous legend
// - preview classified legend
// - full legends with labels, title, etc.

Expand Down Expand Up @@ -322,14 +324,14 @@ define(
legendSquares.sort((a, b) => d3.ascending(a.i, b.i));
this.parentNode.appendChild(this)
// Show tooltip
if (d.label || d.value) {
if (d.label || d.value || d.value === 0) {
$(this).tooltip({
placement: 'bottom',
trigger: 'manual',
title: d.label || d.value,
container: view.$el,
animation: false,
template: '<div class="tooltip '+ view.classes.tooltip +'"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>'
template: '<div class="tooltip ' + view.classes.tooltip + '"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>'
}).tooltip('show')
}
}
Expand All @@ -349,6 +351,117 @@ define(
}
},

/**
* Creates a preview legend for continuous color palettes and inserts it into the
* view
* @param {AssetColorPalette} colorPalette - The AssetColorPalette that maps
* feature attributes to colors, used to create the legend
*/
renderContinuousPreviewLegend: function (colorPalette) {
try {
if (!colorPalette) {
return
}
const view = this
// Data to use in d3
let data = colorPalette.get('colors').toJSON();
// The max width of the SVG
let width = this.previewSvgDimensions.width
// The height of the SVG
const height = this.previewSvgDimensions.height
// Height of the gradient rectangle, leaving some room for the drop shadow
const gradientHeight = height * 0.92

// A unique ID for the gradient
const gradientId = 'gradient-' + view.cid;

// SVG element
const svg = this.createSVG({
dropshadowFilter: false,
width: width,
height: height,
})

// Add the preview class and dropshadow to the SVG
svg.classed(this.classes.previewSVG, true)
svg.style('filter', 'url(#dropshadow)')

// Create a gradient using the data
const gradient = svg.append('defs')
.append('linearGradient')
.attr('id', gradientId)
.attr('x1', '0%')
.attr('y1', '0%')

// Add the gradient stops
data.forEach(function (d, i) {
gradient.append('stop')
// offset should be relative to the value in the data
.attr('offset', d.value / data[data.length - 1].value * 100 + '%')
.attr('stop-color', `rgb(${d.color.red * 255},${d.color.green * 255},${d.color.blue * 255})`)
})

// Create the rectangle
const rect = svg.append('rect')
.attr('x', 0)
.attr('y', 0)
.attr('width', width)
.attr('height', gradientHeight)
.attr('rx', (gradientHeight * 0.1))
.style('fill', 'url(#' + gradientId + ')')

// Create a proxy element to attach the tooltip to, so that we can move the
// tooltip to follow the mouse (by moving the proxy element to follow the mouse)
const proxyEl = svg.append('rect').attr('y', gradientHeight)

rect.on('mousemove', function () {
if (view.model.get('visible')) {
// Get the coordinates of the mouse relative to the rectangle
let xMouse = d3.mouse(this)[0];
if (xMouse < 0) {
xMouse = 0;
}
if (xMouse > width) {
xMouse = width;
}
// Get the relative position of the mouse to the gradient
const relativePosition = xMouse / width;
// Get the value at the relative position by interpolating the data
let value = d3.interpolate(data[0].value, data[data.length - 1].value)(relativePosition);
// Show tooltip with the value
if (value || value === 0) {
// Round to 1 decimal place
value = Math.round(value * 10) / 10
// Move the proxy element to follow the mouse
proxyEl.attr('x', xMouse)
// Attach the tooltip to the proxy element. Tooltip needs to be
// refreshed every time the mouse moves
$(proxyEl).tooltip('destroy');
$(proxyEl).tooltip({
placement: 'bottom',
trigger: 'manual',
title: value,
container: view.$el,
animation: false,
template: '<div class="tooltip ' + view.classes.tooltip + '"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>'
}).tooltip('show')
}
}
})
// Hide tooltip
.on('mouseleave', function () {
$(proxyEl).tooltip('destroy');
})

}
catch (error) {
console.log(
'There was an error rendering a continuous preview legend in a LegendView' +
'. Error details: ' + error
);
}
},

/**
* Creates an SVG element and inserts it into the view
* @param {object} options Used to configure parts of the SVG
Expand Down

0 comments on commit 9da7824

Please sign in to comment.