Условное форматирование цветового градиента с жесткими остановками
У меня есть столбец данных в листе Excel, который имеет положительные и отрицательные значения. То, что я хочу сделать, это применить условное форматирование (градиент цвета) от зеленого до светло-зеленого для положительных значений и от красного до темно-красного для отрицательных значений.
Однако, похоже, я не могу этого сделать. Если я применяю условный формат от, скажем, наибольшего значения до нуля, с нулевым значением как светло-зеленый, тогда все отрицательные значения также будут светло-зелеными. Есть ли способ сделать условный формат применимым только к определенному значению, а не дальше? Аналогичным образом можно сделать условный формат для отрицательных значений, но опять же он будет окрашивать положительные значения светло-красного цвета. Если у меня есть оба на одном листе, то выигрывает наивысший приоритет.
Обновить. Хотя это действительно уродливо, я решил попытаться выяснить, какие ячейки больше 0 (или фактически среднее значение, ~ 1.33
в этом случае) и которые ниже и явным образом устанавливают ссылки на ячейки для этих ячеек. Поэтому я пробовал определенное условное форматирование, подобное этому (положительная зеленая шкала):
<x:conditionalFormatting sqref="$E$5 $E$6 $E$10 $E$13 $E$15 $E$17 $E$18 $E$19 $E$22 $E$24 $E$25..." xmlns:x="http://schemas.openxmlformats.org/spreadsheetml/2006/main">
<x:cfRule type="colorScale" priority="1">
<x:colorScale>
<x:cfvo type="num" val="1.13330279612636" />
<x:cfvo type="num" val="1.91050388235334" />
<x:color rgb="d6F4d6" />
<x:color rgb="148621" />
</x:colorScale>
</x:cfRule>
</x:conditionalFormatting>
И вроде этого (отрицательная красная шкала):
<x:conditionalFormatting sqref="$E$4 $E$7 $E$8 $E$9 $E$11 $E$12 $E$14 $E$16 $E$20 $E$21 $E$23 $E$26 $E$28 $E$29 $E$30..." xmlns:x="http://schemas.openxmlformats.org/spreadsheetml/2006/main">
<x:cfRule type="colorScale" priority="1">
<x:colorScale>
<x:cfvo type="num" val="0.356101709899376" />
<x:cfvo type="num" val="1.13330279612636" />
<x:color rgb="985354" />
<x:color rgb="f4dddd" />
</x:colorScale>
</x:cfRule>
</x:conditionalFormatting>
И это отлично работает! До тех пор, пока вы не попытаетесь сортировать (у меня есть автоматический фильтр на этом листе), и он закручивает назначение ячеек. Итак, теперь у меня есть значения, превышающие 1.33
, которые должны (и были) применены к правилам зеленого градиента, но теперь они ссылаются на красный градиент (и поэтому заканчиваются бледно-красным).
Я попытался использовать как относительные, так и абсолютные ссылки на ячейки (т.е. минус $
), но это тоже не работает.
Ответы
Ответ 1
Мне не удалось найти способ сделать эту работу с использованием условного форматирования по умолчанию Excel. В VBA можно создать собственный условный алгоритм форматирования, который позволит включить эту функцию:
Sub UpdateConditionalFormatting(rng As Range)
Dim cell As Range
Dim colorValue As Integer
Dim min, max As Integer
min = WorksheetFunction.min(rng)
max = WorksheetFunction.max(rng)
For Each cell In rng.Cells
If (cell.Value > 0) Then
colorValue = (cell.Value / max) * 255
cell.Interior.Color = RGB(255 - colorValue, 255, 255 - colorValue)
ElseIf (cell.Value < 0) Then
colorValue = (cell.Value / min) * 255
cell.Interior.Color = RGB(255, 255 - colorValue, 255 - colorValue)
End If
Next cell
End
End Sub
В приведенном выше коде будет создана следующая цветовая схема и может быть легко изменена, чтобы соответствовать любой палитре, которую вы имеете в виду:
![условный формат градиента]()
Вы можете использовать этот код в макросе или перенести его в событие Worksheet_Change и обновить его автоматически (обратите внимание, что при установке в обработчике событий Worksheet_Change вы потеряете функциональность отмены):
Sub Worksheet_Change(ByVal Target As Range)
UpdateConditionalFormatting Range("A1:A21")
End Sub
Ответ 2
Я думал, что это будет относительно легко, но это заняло больше мысли и не является изящным решением.
Предполагая, что у вас есть фон vba, я дам вам метод, который я бы использовал, - если вам нужно программирование, оставьте комментарий, и я окажу поддержку.
Предположение: диапазон сортируется Min-Max или Max-Min 'Это не работает в противном случае
Напишите макрос уровня листа, который обновляется при расчете или выборе - всякий раз, когда вы хотите обновить условное форматирование
в этом макросе вы определяете верхнюю и нижнюю границы вашего диапазона данных и местоположение средней точки
поэтому на рисунке выше будет
LB = A1
UP = A21
MP = A11
Затем вы просто примените два градиента w/оператор if
вы сказали, что средняя точка никогда не будет точной, поэтому оператор if определит, относится ли средняя точка к верхнему или нижнему диапазону
тогда просто:
Range(LB:MP).Select .......apply traditional conditional format 1 (CF1)
Range(MP+1:UP).Select .......apply traditional conditional format 2 (CF2)
or
Range(LB:MP-1).Select .......apply traditional conditional format 1
Range(MP:UP).Select .......apply traditional conditional format 2
Я бы не использовал белый цвет MP, но в CF1, если это красный диапазон, я бы использовал светло-красный и темно-красный, а CF2 светло-зеленый - темно-зеленый
-------------------------------------- Edit ----- ---------------------------------
Я просто прочитал вашу дилемму своего рода.
Другое решение, которое я использовал в прошлом, и снова, если вам нужна поддержка кодирования, я могу попытаться найти старый код
Я использовал простую регрессию на RGB (еще проще, если вы только собираетесь G или R), чтобы фактически присвоить номер цвета каждому значению
MP = (0,1,0)
UP = (0,255,0)
MP-1 = (1,0,0)
LB = (255,0,0)
снова с тем же макросом листа и MP, если логика, как указано выше
а затем я просто повторил через ячейки и применил цвет
if cellVal < MP then CellVal*Mr+Br 'r for red, M & B for slope & intercept
if cellVal > MP then CellVal*Mg+Bg 'g for green, M & B for slope & intercept
Если это неясно, дайте мне знать, и снова, если вам нужна помощь с кодом, который я могу предоставить.
-E
Изменить 2:
Вы могли бы, и я рекомендовал бы вместо повторения всего диапазона, только итерации через видимый диапазон - это ускорит его еще больше, и вы можете добавить триггер в команду sort/filter вашей таблицы/набор данных - это также даст вам свободу выбора, если вы хотите, чтобы спектр цвета основывался на всех ваших данных или только на видимых данных - с последним вы могли бы сделать некоторые "крутые" вещи, такие как взгляд чуть выше 95-го процентиля и все еще видят различия в цветах, где, как и в случае с бывшими, они, скорее всего, будут G 250-255 и сложнее распознать
Ответ 3
После вашего комментария к предыдущему answer предыдущему ответу, я не думаю, что это возможно с помощью colorScale
, так как вам понадобятся две шкалы или одна с четырьмя цвета (ни один из которых не разрешен). Вы можете создать свой собственный, используя условные форматы с формулами.
Используя этот подход, вы можете заставить его работать без необходимости в VBA, и любая сортировка или редактирование листа все равно будут работать.
Я собрал (очень) примерный пример, который показывает, как это может сработать. Это немного грубо, так как он создаст новый условный формат для каждого значения; было бы более аккуратно создать один для каждого диапазона, который вас интересует (возможно, используя процентили), но это отправная точка.
Основная часть работы выполняется в следующих двух методах. Я добавил несколько сводных комментариев к ним, если им нужно больше объяснений, просто дайте мне знать.
/// <summary>
/// Adds a conditional format to the sheet based on the value passed in
/// </summary>
/// <param name="value">The value going into the cell</param>
/// <param name="minValue">The minimum value in the whole range of values going into the sheet</param>
/// <param name="maxValue">The maximum value in the whole range of values going into the sheet</param>
/// <param name="ignoreRangeLowValue">The lowest value in the mid-point. A value greater than or equal to this and less than or equal to the ignoreRangeHighValue will be unstyled</param>
/// <param name="ignoreRangeHighValue">The highest value in the mid-point. A value greater than or equal to the ignoreRangeLowValue and less than or equal to this value will be unstyled</param>
/// <param name="lowValuesMinColor">The colour of the lowest value below the mid-point</param>
/// <param name="lowValuesMaxColor">The colour of the highest value below the mid-point</param>
/// <param name="highValuesMinColor">The colour of the lowest value above the mid-point</param>
/// <param name="highValuesMaxColor">The colour of the highest value above the mid-point</param>
/// <param name="differentialFormats">A DifferentialFormats object to add the formats to</param>
/// <param name="conditionalFormatting">A ConditionalFormatting object to add the conditional formats to</param>
private static void AddConditionalStyle(decimal value,
decimal minValue,
decimal maxValue,
decimal ignoreRangeLowValue,
decimal ignoreRangeHighValue,
System.Drawing.Color lowValuesMinColor,
System.Drawing.Color lowValuesMaxColor,
System.Drawing.Color highValuesMinColor,
System.Drawing.Color highValuesMaxColor,
DifferentialFormats differentialFormats,
ConditionalFormatting conditionalFormatting)
{
System.Drawing.Color fillColor;
if (value >= ignoreRangeLowValue && value <= ignoreRangeHighValue)
return;
if (value > ignoreRangeHighValue)
{
fillColor = GetColour(value, ignoreRangeHighValue, maxValue, highValuesMinColor, highValuesMaxColor);
}
else
{
fillColor = GetColour(value, minValue, ignoreRangeLowValue, lowValuesMinColor, lowValuesMaxColor);
}
DifferentialFormat differentialFormat = new DifferentialFormat();
Fill fill = new Fill();
PatternFill patternFill = new PatternFill();
BackgroundColor backgroundColor = new BackgroundColor() { Rgb = fillColor.Name };
patternFill.Append(backgroundColor);
fill.Append(patternFill);
differentialFormat.Append(fill);
differentialFormats.Append(differentialFormat);
ConditionalFormattingOperatorValues op = ConditionalFormattingOperatorValues.Between;
Formula formula1 = null;
Formula formula2 = null;
if (value > maxValue)
{
op = ConditionalFormattingOperatorValues.GreaterThanOrEqual;
formula1 = new Formula();
formula1.Text = value.ToString();
}
else if (value < minValue)
{
op = ConditionalFormattingOperatorValues.LessThanOrEqual;
formula1 = new Formula();
formula1.Text = value.ToString();
}
else
{
formula1 = new Formula();
formula1.Text = (value - 0.05M).ToString();
formula2 = new Formula();
formula2.Text = (value + 0.05M).ToString();
}
ConditionalFormattingRule conditionalFormattingRule = new ConditionalFormattingRule()
{
Type = ConditionalFormatValues.CellIs,
FormatId = (UInt32Value)formatId++,
Priority = 1,
Operator = op
};
if (formula1 != null)
conditionalFormattingRule.Append(formula1);
if (formula2 != null)
conditionalFormattingRule.Append(formula2);
conditionalFormatting.Append(conditionalFormattingRule);
}
/// <summary>
/// Returns a Color based on a linear gradient
/// </summary>
/// <param name="value">The value being output in the cell</param>
/// <param name="minValue">The minimum value in the whole range of values going into the sheet</param>
/// <param name="maxValue">The maximum value in the whole range of values going into the sheet</param>
/// <param name="minColor">The color of the low end of the scale</param>
/// <param name="maxColor">The color of the high end of the scale</param>
/// <returns></returns>
private static System.Drawing.Color GetColour(decimal value,
decimal minValue,
decimal maxValue,
System.Drawing.Color minColor,
System.Drawing.Color maxColor)
{
System.Drawing.Color val;
if (value < minValue)
val = minColor;
else if (value > maxValue)
val = maxColor;
else
{
decimal scaleValue = (value - minValue) / (maxValue - minValue);
int r = (int)(minColor.R + ((maxColor.R - minColor.R) * scaleValue));
int g = (int)(minColor.G + ((maxColor.G - minColor.G) * scaleValue));
int b = (int)(minColor.B + ((maxColor.B - minColor.B) * scaleValue));
val = System.Drawing.Color.FromArgb(r, g, b);
}
return val;
}
В качестве примера я создал это:
static uint formatId = 0U;
public static void CreateSpreadsheetWorkbook(string filepath)
{
SpreadsheetDocument spreadsheetDocument = SpreadsheetDocument.
Create(filepath, SpreadsheetDocumentType.Workbook);
// Add a WorkbookPart to the document.
WorkbookPart workbookpart = spreadsheetDocument.AddWorkbookPart();
workbookpart.Workbook = new Workbook();
// Add a WorksheetPart to the WorkbookPart.
WorksheetPart worksheetPart = workbookpart.AddNewPart<WorksheetPart>();
SheetData sheetData = new SheetData();
worksheetPart.Worksheet = new Worksheet(sheetData);
// Add Sheets to the Workbook.
Sheets sheets = spreadsheetDocument.WorkbookPart.Workbook.
AppendChild<Sheets>(new Sheets());
// Append a new worksheet and associate it with the workbook.
Sheet sheet = new Sheet()
{
Id = spreadsheetDocument.WorkbookPart.GetIdOfPart(worksheetPart),
SheetId = 1,
Name = "FormattedSheet"
};
sheets.Append(sheet);
WorkbookStylesPart stylesPart = workbookpart.AddNewPart<WorkbookStylesPart>();
stylesPart.Stylesheet = new Stylesheet();
Fills fills = new Fills() { Count = (UInt32Value)20U }; //this count is slightly out; we should calculate it really
//this could probably be more efficient - we don't really need one for each value; we could put them in percentiles for example
DifferentialFormats differentialFormats = new DifferentialFormats() { Count = (UInt32Value)20U };
ConditionalFormatting conditionalFormatting = new ConditionalFormatting() { SequenceOfReferences = new ListValue<StringValue>() { InnerText = "A1:A21" } };
for (decimal i = 1; i > -1.1M; i -= 0.1M)
{
AddConditionalStyle(i, -0.8M, 0.8M, 0M, 0M,
System.Drawing.Color.FromArgb(152, 83, 84),
System.Drawing.Color.FromArgb(244, 221, 221),
System.Drawing.Color.FromArgb(214, 244, 214),
System.Drawing.Color.FromArgb(20, 134, 33),
differentialFormats,
conditionalFormatting);
}
worksheetPart.Worksheet.Append(conditionalFormatting);
stylesPart.Stylesheet.Append(differentialFormats);
uint rowId = 1U;
for (decimal i = 1; i > -1.1M; i -= 0.1M)
{
Cell cell = new Cell();
cell.DataType = CellValues.Number;
cell.CellValue = new CellValue(i.ToString());
Row row = new Row() { RowIndex = rowId++ };
row.Append(cell);
sheetData.Append(row);
}
workbookpart.Workbook.Save();
spreadsheetDocument.Close();
}
Создает таблицу, которая выглядит так:
![введите описание изображения здесь]()
Ответ 4
Возможно, вы можете использовать тип num
на cfvo
, чтобы определить среднюю точку как ноль с белым цветом. Затем установите min
на красный, а max
на зеленый.
Что-то вроде этого, например
<conditionalFormatting sqref="A1:A21">
<cfRule type="colorScale" priority="1">
<colorScale>
<cfvo type="min" />
<cfvo type="num" val="0" />
<cfvo type="max" />
<color rgb="ff0000" />
<color rgb="ffffff" />
<color rgb="00ff00" />
</colorScale>
</cfRule>
</conditionalFormatting>
дает результат, который выглядит так:
![введите описание изображения здесь]()
Ответ 5
Основываясь на ваших дальнейших комментариях, я вижу, что ваша особая забота об использовании трехцветного градиента - это отсутствие различий вокруг точки перехода. Я рекомендую на основании этой заметки, что на самом деле вы используете несколько наборов условных правил форматирования на перекрывающихся разделах с определенным приоритетом, как показано ниже:
Предположим, что мы смотрим на столбец A, который будет содержать цифры от -100 до 100. Предположим, что вы хотите, чтобы что-то -100 или хуже было ярко-красным, с постепенным исчезновением до нуля около 0. Затем, скажем, от +5 до -5, вам нужен бесцветный белый цвет. Затем около 0 оно должно быть светло-зеленого, до ярко-зеленого на +100.
Сначала установите правило относительно раздела "0". Что-то вроде:
=ROUND(A1,0)=0
Примените это правило в приоритетном порядке и установите его, чтобы сделать ячейку белой. Обратите внимание, что вы также можете использовать это для "белых" удаленных случаев. Что-то вроде:
=OR(ROUND(A1,0)=0,ROUND(A1,0)>100,ROUND(A1,0)<-100)
Это правило сделает ячейки в 0 белых и вне вашего желаемого белого диапазона -100- > 100.
Затем примените второе правило, которое включает в себя ваши градиенты. Это установило бы 3 цвета, с белым на 0 (даже если ваше жестко закодированное "округленное до 0" правило применило бы вскрытие, исключающее постепенный цвет вокруг числа 0), красный на -100 и зеленый на 100.
На этой основе все вне диапазона диапазона -100- > 100 будет белого цвета, все, что округляет до 0, будет белым, а любое другое число в диапазоне будет равномерно перемещаться от ярко-красного, белого до ярко-зеленого.
Ответ 6
Я просто начинающий vba, и я подумал, что это интересный вопрос. Я не уверен, как правила применяются для публикации решений "мозгового штурма", поэтому, если я наступаю на некоторые пальцы ног, дайте мне знать, и я удалю свой ответ и научусь избегать этого в будущем. Смиренное введение:
Можете ли вы изменить условное форматирование через vba? Я бы исследовал, можно ли это сделать: чтение свойств форматирования и объектов в условном форматировании vba представляется возможным использовать TintAndShade
Правило №1: Цвет все, что больше, чем желаемый серебристый оттенок серебра (по формуле)
Правило №2: Противоположная сторона в моноколорном красном
Правило № 3: трехсторонний градиент → изменен в vba
Измените его - удалите ссылки, но добавьте строку, в которой вы установили
.TintAndShade = .Gradient
внутри IF
IF .Gradient < 0.3 Then .TintAndShade = 0.3 Else .TintAndShade = .Gradient End if
(0.3 - это мое предложение. По крайней мере, на моем экране зеленый цвет @0,3 оттенка заметно отличается от красного @0.3)