I have an image type field in Sanity (docs) and I need to make sure that the dimensions are within a particular range to avoid breaking the website they're going on. Sanity offers validations, but the image type only has “required” and “custom” rules, and the field information passed into the custom validator doesn't include the image metadata.
How can I work around this limitation and offer in-CMS validation of the dimensions?
While Sanity doesn't pass the image metadata into the validator, you can extract image format and dimension information from the asset ID that is provided. According to this documentation, this is a supported, stable way of accessing this information without loading the image object from Sanity.
For example, here's the first argument passed to the Rule.custom validator:
{
"_type": "image",
"alt": "Example Image",
"asset": {
"_ref": "image-bff149a0b87f5b0e00d9dd364e9ddaa0-700x650-jpg",
"_type": "reference"
}
}
Getting to the image dimensions can be accomplished like this:
{
title: "My Image",
name: "image",
type: "image",
options: {
accept: "image/*",
},
validation: Rule => Rule.custom(image => {
if (!image) return true
const { dimensions } = decodeAssetId(image.asset._ref)
return dimensions.width >= 500 || "Image must be wider"
}
}
const pattern = /^image-([a-f\d]+)-(\d+x\d+)-(\w+)$/
const decodeAssetId = id => {
const [, assetId, dimensions, format] = pattern.exec(id)
const [width, height] = dimensions.split("x").map(v => parseInt(v, 10))
return {
assetId,
dimensions: { width, height },
format,
}
}
I have also rolled this functionality into the sanity-pills library that makes it easier to do like this:
import { createImageField } from "sanity-pills"
createImageField({
name: "image",
validations: {
required: true,
minWidth: 500,
maxHeight: 9000
},
})
Related
My data looks like below:
var data = [{
"Id": "1",
"startTime": 1554750660000,
"endTime": 1554751320000,
"Alerts": [
"Alert1","Alert2"
],
"Apps": [
"App1","App2"
]
}]
I have to display above data using crossfilter-data-table.
Currently, I am able to display the data like below:
StartTime EndTime Apps Alerts
04/08/2019 12
12:42:00 PM 12:49:00 PM App1,App2 Alert1,Alert2
Instead, I want to show it in below format.
Changes are: the array values of columns Apps, Alerts are not displayed as comma separated values. Instead they are shown in a separate line one after the other.
StartTime EndTime Apps Alerts
04/08/2019 12
12:42:00 PM 12:49:00 PM App1 Alert1
App2 Alert2
My current code looks like below:
import React from "react";
import * as dc from "dc";
import "dc/dc.css";
import * as d3 from "d3";
import { ChartTemplate } from "./chartTemplate";
import { css } from "glamor";
const tableFunc = (divRef, ndx) => {
const summaryTable = dc.dataTable(divRef);
const dimension = ndx.dimension(d => d.StartTime);
summaryTable
.dimension(dimension)
.showGroups(true)
.size(10000)
.group(d => {
return d.hour;
})
.columns([
{
label: "Start Time",
format: function(d) {
return d. startTime;
}
},
{
label: "End Time",
format: function(d) {
return d. endTime;
}
},
{
label: "Apps",
format: function(d) {
return d.Apps;
}
},
{
label: "Alerts",
format: function(d) {
return d.Alerts;
}
}
])
.sortBy(function(d) {
return d.startTime;
})
.order(d3.descending)
.on("renderlet", function(table) {
table.selectAll(".dc-table-group").classed("info", true);
});
return summaryTable;
};
const style = css({
"& tr": {
"&:hover": {
background: "#dddd"
}
},
"& td": {
textAlign: "left",
borderTop: "1px solid #ddd"
}
});
export const DataTable = props => (
<ChartTemplate
chartFunction={tableFunc}
styles={style}
title="Summary"
/>
);
What change i should do to display details in required format as above.Please help I'm really new to react, crossfilter and d3.
Assuming it's okay to display all the array values in one cell, I think the easiest way to get what you want would be simply to format the cell using <br> - the line break element.
E.g.
{
label: "Apps",
format: function(d) {
return d.Apps.join('<br>');
}
},
It would be a lot more difficult to create extra rows, since data table rows usually correspond 1:1 with rows of the data set.
Adding links
#zubug55 commented
In my real data, alerts look like:
"Alerts": [ "abc#link1","def#link2" ]
How do I break each abc#link1 on # and make abc a link to the url link1?
It's probably easiest to generate the anchor elements in HTML at the same time, like
return d.Alerts.map(function(a) {
var parts = a.split('#');
return '' + parts[0] + '';
}).join('<br>');
Im sending my json data through controller like following:i have written the query here just to prevent making it complicated and messy :
My Controller Returning This:
public JsonResult powerConverter(string regionalManager)
foreach (DataRow dt in dt_power_conv.Rows)
{
_powerConv.turbineName.Add(dt["turbine_name"].ToString());
_powerConv.duration_hrs.Add(double.Parse(dt["duration_hrs"].ToString()));
_powerConv.abb_conv.Add(dt["abb_conv"].ToString());
_powerConv.eei_conv.Add(dt["eei_conv"].ToString());
_powerConv.leit_drive_conv.Add(dt["leit_drive_conv"].ToString());
}
return Json(_powerConv, JsonRequestBehavior.AllowGet);
}
in my view I get it with an Ajax call and simply bind my chart with it:
$.ajax({
dataType: "json",
type: "POST",
url: "#Url.Action("powerConverter","Ranking")",
contentType: "application/json; charset=utf-8",
data: JSON.stringify({ "regionalManager": tmpString }),
success: function (result) {
debugger;
$("#powerChart").kendoChart({
dataSource: {
data: result
},
chartArea: {
background: "#fcfcfc",
},
series: [{
axis: "l100km",
type: "column",
// name: "DURATION",
color: "#008080",
field: "duration_hrs",
categoryField: "turbineName"
},
],
categoryAxis: {
axisCrossingValue: [0, 20],
majorGridLines: {
visible: false
},
line: {
visible: true
},
labels: {
rotation: 340
},
},
tooltip: {
visible: true,
// majorUnit:10,
template: " #= value #"
},
});
}
});
I also posted the screen shot of my json,but still its not working,i set the categoryField and field with the exact name im getting from json but the chart shows nothing
It looks like the controller is returning two arrays, one for errorDuration and one for turbineName. Try changing the controller to return an array of objects.
You would want a review of returned json to show
[0] = { duration: 1, turbine: "a" }
[1] = { duration: 2, turbine: "b" }
[2] = { duration: 3, turbine: "c" }
In the chart the config settings for the series the field names have to match exactly the property names of the data elements, thus
field: "duration",
categoryField: "turbine",
Added
The controller code appears to be populating a list of a model class whose fields are also lists. Try updating it to return the Json for a list of objects
For quickness this example shows how using anonymous objects. Strongly typed objects are highly recommended for robustness and Visual Studio intellisense. The field names that you use in your kendo chart configuration will be "turbine_name" and "duration_hours"
// This technique copied from #Paul Rouleau answer to
// https://stackoverflow.com/questions/612689/a-generic-list-of-anonymous-class
// initialize an empty list that will contain objects having two fields
var dataForJson = new List<Tuple<string, double>>()
.Select(t => new {
turbine_name = t.Item1,
duration_hours = t.Item2 }
).ToList();
// go through data table and move data into the list
foreach (DataRow row in myDataTable.Rows)
{
dataForJson.Add (new {
turbine_name = (string)row["turbine_name"],
duration_hours = (double)row["duration_hours"]
});
}
return Json(dataForJson, JsonRequestBehavior.AllowGet);
Note, if you do further research you will find numerous other ways to convert a data table into a Json
I've been trying for 3 days to get this chart to display the way I want it to. Everything was working 100% until I realized the grouped bar chart numbers were off.
Example: When the bottom bar value equals 10 and the top bar value equals 20, the top of the grouped bar read 30. This is the default behavior, but not how I want to represent my data. I want the top of the grouped bar to read whatever the highest number is, which lead me to this fiddle representing the data exactly how I wanted to.
After refactoring my logic, this is what I have so far. As you can see the timeseries line is broken up and the tooltip is not rendering the group of data being hovered over.
My questions:
1) How to get the tooltip to render all three data points (qty, price, searches)
2) How to solidify the timeseries line so it's not disconnected
Any help would be greatly appreciated so I can move on from this 3 day headache!
Below is most of my code - excluding the JSON array for brevity, which is obtainable at my jsfiddle link above. Thank you in advance for your time.
var chart = c3.generate({
bindto: '#chart',
data: {
x: 'x-axis',
type: 'bar',
json: json,
xFormat: '%Y-%m-%d',
keys: {
x: 'x-axis',
y: 'searches',
value: ['qty', 'searches', 'price']
},
types: {
searches: 'line'
},
groups: [
['qty', 'price']
],
axes: {
qty: 'y',
searches: 'y2'
},
names: {
qty: 'Quantity',
searches: 'Searches',
price: 'Price ($)'
},
colors: {
price: 'rgb(153, 153, 153)',
qty: 'rgb(217, 217, 217)',
searches: 'rgb(255, 127, 14)'
}
},
bar: {
width: {
ratio: 0.60
}
},
axis: {
x: {
type: 'timeseries',
label: { text: 'Timeline', position: 'outer-right' },
tick: {
format: '%Y-%m-%d'
}
},
y: {
type: 'bar',
label: {
text: 'Quantity / Price',
position: 'outer-middle'
}
},
y2: {
show: true,
label: {
text: 'Searches',
position: 'outer-middle'
}
}
},
tooltip: {
grouped: true,
contents: function(d, defaultTitleFormat, defaultValueFormat, color) {
var data = this.api.data.shown().map(function(series) {
var matchArr = series.values.filter(function(datum) {
return datum.value != undefined && datum.x === d[0].x;
});
if (matchArr.length > 0) {
matchArr[0].name = series.id;
return matchArr[0];
}
});
return this.getTooltipContent(data, defaultTitleFormat, defaultValueFormat, color);
}
}
});
1) If I got it right, you want tooltip to show all values, even if some of them are null.
Null values are hidden by default. You can replace them with zero (if it is suitable for your task) and thus make them visible.
Also, it seems to me that there is a shorter way to get grouped values:
var data = chart.internal.api.data().map(function(item) {
var row = item.values[d[0].index]; // get data for selected index
if (row.value === null) row.value = 0; // make null visible
return row;
});
2) I think you are talking about line.connectNull option:
line: {
connectNull: true
}
UPDATE
Looks like having duplicate keys breaks work of api.data() method.
You need to change json structure to make keys unique:
Before:
var json = [
{"x-axis":"2017-07-17","qty":100},
{"x-axis":"2017-07-17","price":111},
{"x-axis":"2017-07-17","searches":1},
{"x-axis":"2017-07-18","qty":200},
{"x-axis":"2017-07-18","price":222},
{"x-axis":"2017-07-18","searches":2}
];
After:
var json = [
{"x-axis":"2017-07-17","qty":100,"price":111,"searches":1},
{"x-axis":"2017-07-18","qty":200,"price":222,"searches":2}
];
See fiddle.
I am new to Vega and Vega-Lite. I am creating a simple bar chart using Vega-Lite but I am not able to add any event listeners e.g. "hover".
I want to hover a bar and change the color of the bar.
If you're using Vega-Embed, it returns a promise with a reference to the view which allows you to use addEventListener - explained in the docs here.
Here is an example:
const width = 600
const color = blue
embed(element, {
$schema: 'https://vega.github.io/schema/vega-lite/3.0.0-rc6.json',
data: { 'values': data },
mark: {
type: 'line',
color,
point: {
color,
}
},
width,
height: width / 2,
encoding: {
'x': {
field: 'label',
type: 'temporal',
},
'y': {
field: 'value',
type: 'quantitative',
},
}
}).then(({spec, view}) => {
view.addEventListener('mouseover', function (event, item) {
console.log(item.datum)
})
})
I am using jqplot to render a line graph.
My series data looks like:
results =[
['1/1/2014', 1000],
['2/1/2014', 2000],
['3/1/2014', 3000],
['4/1/2014', 4000],
['5/1/2014', null]
];
my call to jqplot looks something like
$.jqplot('myChart', results,
{
series: [
{
rendererOptions: {
bands: {
show: true,
interval: '10%'
},
}
}
]
});
The chart will render, but it will be missing the 10% bands above and below.
If i change the null value
['5/1/2014', null]
to be
['5/1/2014', 5000]
then the bands will render correctly.
My does data have some missing values. Is there any way to make the bands render for non-null data points on the line, even if the line does have some null data points?
Instead of sending null for those values, omit them entirely and depend on the dateAxisRenderer to correctly space the values on the axis.
results = [
['1/1/2014', 1000],
['2/1/2014', 2000],
['3/1/2014', 3000],
['4/1/2014', 4000],
['6/1/2014', 3500]
];
$.jqplot ('myChart', [results],
{
series: [{
rendererOptions: {
bands: {
show: true,
interval: '10%'
}
}
}],
axes: {
xaxis: {
renderer: $.jqplot.DateAxisRenderer
}
}
});
JSFiddle version here (don't forget the extra script reference for dateAxisRenderer)
http://jsfiddle.net/4vmNf/1/
Alternatively, you can pass separate arrays for upper & lower band data, and this does not have to follow the same intervals as the underlying data array.
lowerBand = [];
upperBand = [];
for(var i = 0; i < results.length; i++) {
if (results[i][1]) {
lowerBand.push([results[i][0], results[i][1] * 0.9]);
upperBand.push([results[i][0], results[i][1] * 1.1]);
} else {
// not clear to me how you want to calculate band for missing values
lowerBand.push([results[i][0], 3500]);
upperBand.push([results[i][0], 6500]);
}
}
And then use bandData option:
series: [{
rendererOptions: {
bandData: [lowerBand, upperBand]
}
}]